开发者社区> 问答> 正文

还原json循环引用对象的一种办法? 400 报错

还原json循环引用对象的一种办法? 400 报错

后端数据实体都是由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));
                                else
                                    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));
                                    else
                                        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遍的结果:


 

browsers 用时:ms
chrome 28 20-30
firefox 22 20-35
ie6 300-400
ie9 23-30




@wenshao

 目前来看,基本还算满意,不知谁有更好的方法希望也能拿出来分享一下。

展开
收起
爱吃鱼的程序员 2020-05-30 22:21:39 1185 0
1 条回答
写回答
取消 提交回答
  • https://developer.aliyun.com/profile/5yerqm5bn5yqg?spm=a2c6h.12873639.0.0.6eae304abcjaIB

    既然dojo有,何不把dojo的借鉴一下.######对dojo不是很熟悉,没时间仔细研究。。。######不错,我一直希望有人能够做这个事情,在客户端解析fastjson的应用。######回复 @gohsy : 谢谢的你支持。使用好了并参与其中,才是更好的使用开源方式。也就是所谓的社区能读能改。我打算开一个项目用javascript实现fastjson的引用解析,希望你能够参与其中。######很早就在项目中引入了温少侠的fastjson druid,绝对达到商业软件的水准了,屡用不爽,越用越爽。######

    fastjson循环引用的文档:

    https://github.com/alibaba/fastjson/wiki/%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8

    ######很高端。只是想知道,大部分语言的JSON API应该都不支持循环引用吧,那么循环引用是什么样的需求产生的?可以避免不?
    ######文章里面已经描述了,由hibernate生成的实体,包含着大量的关联引用,在稍大的一点的项目中,实体对象之间的关联关系会比较复杂,要么就手动处理有选择性的输出关联关系,要么就用fastjson这样能够处理循环引用的库,在数据使用方的底层,在做引用还原。######可以对每个域模型继承一个接口,接口提供一个将模型转为map的方法,map里可以包含引用,但也是对方转成map的,同时自己在转map那个方法里防止递归引用。######Ext.define('overrides.JSON', {
        override : 'Ext.JSON',

        decode : function(json, safe) {
            me = this;
            if (typeof me.JSON.retrocycle !== 'function') {
                me.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));
                                            else
                                                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));
                                                else
                                                    rez(item);
                                            }
                                            xpath.pop();
                                        }
                                    }
                                }
                            }
                        })($);
                        return $;
                    }
                })();
            }
            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 decodingFunction;
            doDecode = function(json) {
                return json ? eval("(" + json + ")") : "";
            };
            if (!decodingFunction) {
                // setup decoding function on first access
                decodingFunction = isNative() ? JSON.parse : doDecode;
            }
            try {
                return this.JSON.retrocycle(decodingFunction(json));
            } catch (e) {
                if (safe === true) {
                    return null;
                }
                Ext.Error.raise({
                            sourceClass : "Ext.JSON",
                            sourceMethod : "decode",
                            msg : "我尝试解析 an invalid JSON String: " + json
                        });
            }
        }
    });

    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就会错误。

    举个例子:客户与客户联系人。客户有一个客户联系人的集合的属性,客户还有一个主联系人的属性。同时客户联系人也指向客户有一个属性,当这种对应关系的时候解析就会出错!

    我尝试着想要去解决,但是智商有限搞不了。求作者在查看一下。


    看来这个问题还是有人关注的哈。 

    你可以给点数据,我有空的时候的看看。

    2020-05-30 22:21:40
    赞同 展开评论 打赏
问答排行榜
最热
最新

相关电子书

更多
低代码开发师(初级)实战教程 立即下载
冬季实战营第三期:MySQL数据库进阶实战 立即下载
阿里巴巴DevOps 最佳实践手册 立即下载