后端数据实体都是由hibernate生成的,与浏览器客户端交互json时,采用了alibaba FastJson库。
首先要说fastJson的确为众多json类库中数一数二的,api简单易用,性能强悍,测试完整,典型的国产高端货。
由于后端涉及几个数据库三四百张表,生成的实体之间的嵌套关系也非常复杂,再由fastJson将Bean转化为json string时,一个非常的典型的问题就出现了,就是对象间的嵌套循环引用,如果没有合理的json生成策略,那将是一个无底洞的死循环,直到堆栈溢出。(循环引用的数据不能排除掉因为前端需要读取)
(简单说就是A引用B,B引用C,C引用A,实际上比这更复杂的环形引用链)
fastJson内置有合理的循环引用检测,
采用了比较广泛的json path表示法,避免了反射Bean时循环引用造成的死循环。类似于这样的形式 {"$ref":"$.data[1]"}输出,关键看图fastJson采用循环引用后输出结果!
而这种形式,似乎只有dojo的dojox.json.ref提供了相应的parse支持,其它地方似乎没有找到合适的解析方法。所以在前端依然无法得到相应的数据。
研究了一下fastJson的循环引用表示,然后对前端ExtJs的decode部分进行了重写,于是可以几乎完整的还原原来Java Bean之间嵌套引用关系。
项目前端是ExtJS v3.4所以直接对Ext方法进行覆盖。
String.prototype.startsWith = function (prefix) {
return prefix && this.length >= prefix.length && this.substring(0, prefix.length) === prefix;
if (!window.JSON)
JSON = {};
if (typeof JSON.retrocycle !== 'function') {
JSON.retrocycle = (function () {
'use strict';
var t_obj = typeof {}, t_arr = Object.prototype.toString.apply([]) , t_str = typeof "";
var walk = function (path, _xpath, array) {
if (path.startsWith('$')) // 基于xpath直接定位
return path;
else { // 相对回溯定位
var x , j = path.split('..'), k = -j.length + (array ? 2 : 1), last = j.slice(-1)[0].replace('/', '.');
x = k < 0 ? _xpath.slice(0, k) : _xpath.slice(0);
if (last && !last.startsWith('.') && !last.startsWith('['))
last = '.' + last;
path = x.join('.') + last;
return path; // 最终得到绝对xpath地址
return function ($) {
var xpath = ['$'];
(function rez(value) {
var i, item, name, path, _x;
if (value && typeof value === t_obj) {
if (Object.prototype.toString.apply(value) === t_arr) {
for (i = 0; i < value.length; i += 1) {
item = value[i];
if (item && typeof item === t_obj) {
xpath.push(xpath.pop() + '[' + i + ']'); // 下标引用要合并分级
path = item.$ref;
if (typeof path === t_str)
value[i] = eval(walk(path, xpath, true));
rez(item);
if (_x = xpath.pop())
xpath.push(_x.slice(0, _x.indexOf('['))); // 下标引用还原分级
} else {
for (name in value) {
if (value.hasOwnProperty(name) && typeof value[name] === t_obj) {
xpath.push(name);
item = value[name];
if (item) {
path = item.$ref;
if (typeof path === t_str)
value[name] = eval(walk(path, xpath));
rez(item);
xpath.pop();
})($);
return $;
})();
Ext.onReady(function () {
Ext.decode = function () {
var isNative = function () {
var useNative = null;
return function () {
if (useNative === null) {
useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
return useNative;
var dc,
doDecode = function (json) {
return json ? eval("(" + json + ")") : "";
return function (json) {
if (!dc) {
dc = isNative() ? JSON.parse : doDecode;
// return dc(json);
return JSON.retrocycle(dc(json));
Ext.apply(Ext.util.JSON, {
decode: Ext.decode
通过覆盖以上方法,便可以还原到原java Bean的嵌套引用关系。
透过console观察一下json解析后并作了复原循环引用后的对象属性,如图:
可能有人担心性能问题,简单的用两个例子测试了一下,跑Ext.decode() 100遍的结果:
{"$ref":".."} // 引用父对象
{"$ref":"../.."} // 引用父对象的父对象
{"$ref":"$.members[0].reportTo"} // 基于路径的引用
(2013/08/02 15:43)
@gohsy : 谢谢的你支持。使用好了并参与其中,才是更好的使用开源方式。也就是所谓的社区能读能改。我打算开一个项目用javascript实现fastjson的引用解析,希望你能够参与其中。
fastjson循环引用的文档:
https://github.com/alibaba/fastjson/wiki/%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8
Ext.decode = Ext.JSON.decode;
在Extjs 4.2 里的写法。放在与app目录平齐的overrides里面。
然后在APP.js里面加入下面的东西。
Ext.application({
name: 'admin',
extend: 'admin.Application',
requires: [
// 'overrides.grid.RowEditor'
'overrides.JSON'
],
autoCreateViewport: true
});
这个解析的算法还有BUG。就是当A引用B一个集合,A在引用B单个的时候解析出来可能B指向的A就会错误。
举个例子:客户与客户联系人。客户有一个客户联系人的集合的属性,客户还有一个主联系人的属性。同时客户联系人也指向客户有一个属性,当这种对应关系的时候解析就会出错!
我尝试着想要去解决,但是智商有限搞不了。求作者在查看一下。
引用来自“刘思作”的评论
这个解析的算法还有BUG。就是当A引用B一个集合,A在引用B单个的时候解析出来可能B指向的A就会错误。
举个例子:客户与客户联系人。客户有一个客户联系人的集合的属性,客户还有一个主联系人的属性。同时客户联系人也指向客户有一个属性,当这种对应关系的时候解析就会出错!
我尝试着想要去解决,但是智商有限搞不了。求作者在查看一下。
看来这个问题还是有人关注的哈。
你可以给点数据,我有空的时候的看看。