解析来自 JSON 对象的循环引用

Posted

技术标签:

【中文标题】解析来自 JSON 对象的循环引用【英文标题】:Resolve circular references from JSON object 【发布时间】:2013-03-09 15:40:41 【问题描述】:

如果我有来自 json.net 的序列化 JSON,如下所示:

User:id:1,Fooid:1,prop:1,
FooList$ref: "1",Fooid:2,prop:13

我想在 FooList 上通过 foreach 进行淘汰赛输出,但我不确定如何继续,因为 $ref 东西可能会抛出东西。

我认为解决方案是通过不使用以某种方式强制所有 Foo 呈现在 FooList 中:

PreserveReferencesHandling = PreserveReferencesHandling.Objects

但这似乎很浪费..

【问题讨论】:

另一种解决方案:***.com/questions/10747341/… 另见JsonNetDecycle 【参考方案1】:

我发现了一些错误并实现了数组支持:

function resolveReferences(json) 
    if (typeof json === 'string')
        json = JSON.parse(json);

    var byid = , // all objects by id
        refs = []; // references to objects that could not be resolved
    json = (function recurse(obj, prop, parent) 
        if (typeof obj !== 'object' || !obj) // a primitive value
            return obj;
        if (Object.prototype.toString.call(obj) === '[object Array]') 
            for (var i = 0; i < obj.length; i++)
                // check also if the array element is not a primitive value
                if (typeof obj[i] !== 'object' || !obj[i]) // a primitive value
                    continue;
                else if ("$ref" in obj[i])
                    obj[i] = recurse(obj[i], i, obj);
                else
                    obj[i] = recurse(obj[i], prop, obj);
            return obj;
        
        if ("$ref" in obj)  // a reference
            var ref = obj.$ref;
            if (ref in byid)
                return byid[ref];
            // else we have to make it lazy:
            refs.push([parent, prop, ref]);
            return;
         else if ("$id" in obj) 
            var id = obj.$id;
            delete obj.$id;
            if ("$values" in obj) // an array
                obj = obj.$values.map(recurse);
            else // a plain object
                for (var prop in obj)
                    obj[prop] = recurse(obj[prop], prop, obj);
            byid[id] = obj;
        
        return obj;
    )(json); // run it!

    for (var i = 0; i < refs.length; i++)  // resolve previously unknown references
        var ref = refs[i];
        ref[0][ref[1]] = byid[ref[2]];
        // Notice that this throws if you put in a reference at top-level
    
    return json;

【讨论】:

- 添加 if (Object.prototype.toString.call(obj) === '[object Array]') ... - 几乎最新的字符串有错误:ref[0][参考[1]] = byid[参考[2]];但必须是:ref[0][ref[1]] = byid[ref[2]]; - 这个字符串是:obj[prop] = recurse(obj[prop], prop, obj) 变成:obj[prop] = recurse(obj[prop], prop, obj); 非常感谢!我花了很多时间寻找错误! 在数组处理中仍然存在一个错误,如果它是原始的 :::code::: if (typeof obj[i] !== 'object' || !obj[i]) 返回 obj[i]; 这适用于“嵌套”数组吗?我试过没有运气。 正是我想要的【参考方案2】:

您从服务器接收的 json 对象包含Circular References。在使用对象之前,您必须首先从对象中删除所有$ref 属性,这意味着您必须放置此链接指向的对象来代替$ref : "1"

在您的情况下,它可能指向 id 为 1 的用户对象

为此,您应该查看Douglas Crockfords Plugin on github。有一个cycle.js 可以为您完成这项工作。

或者您可以使用以下代码(未测试):

function resolveReferences(json) 
    if (typeof json === 'string')
        json = JSON.parse(json);

    var byid = , // all objects by id
        refs = []; // references to objects that could not be resolved
    json = (function recurse(obj, prop, parent) 
        if (typeof obj !== 'object' || !obj) // a primitive value
            return obj;
        if ("$ref" in obj)  // a reference
            var ref = obj.$ref;
            if (ref in byid)
                return byid[ref];
            // else we have to make it lazy:
            refs.push([parent, prop, ref]);
            return;
         else if ("$id" in obj) 
            var id = obj.$id;
            delete obj.$id;
            if ("$values" in obj) // an array
                obj = obj.$values.map(recurse);
            else // a plain object
                for (var prop in obj)
                    obj[prop] = recurse(obj[prop], prop, obj)
            byid[id] = obj;
        
        return obj;
    )(json); // run it!

    for (var i=0; i<refs.length; i++)  // resolve previously unknown references
        var ref = refs[i];
        ref[0][ref[1]] = byid[refs[2]];
        // Notice that this throws if you put in a reference at top-level
    
    return json;
  

如果有帮助请告诉我!

【讨论】:

如果将 byid[id] = obj 赋值向上移动(在 var id =... 赋值后面),则 refs 数组中的条目会少得多。在我的对象图中,我什么都没有。【参考方案3】:

如果您利用JSON.parsereviver 参数,这实际上非常简单。

示例如下。查看浏览器控制台的输出,因为 *** 的 sn-p 控制台输出无法提供结果的准确图片。

// example JSON
var j = '"$id":"0","name":"Parent","child":"$id":"1", "name":"Child","parent":"$ref":"0","nullValue":null'

function parseAndResolve(json) 
    var refMap = ;

    return JSON.parse(json, function (key, value) 
        if (key === '$id')  
            refMap[value] = this;
            // return undefined so that the property is deleted
            return void(0);
        

        if (value && value.$ref)  return refMap[value.$ref]; 

        return value; 
    );


console.log(parseAndResolve(j));

【讨论】:

嗨,这很好用,但是我遇到了一个错误,对象字段设置为 null。用“if (value && value.$ref)”替换“if (value.$ref)”解决这个问题:-) @Arcord 感谢您解决这个问题并告诉我!答案已更新。 这个很棒! 你如何适应从http get(例如从REST 服务)获取回复?获取失败,如果那样,则不会调用打字稿代码.. @BoppityBop 我认为答案取决于 GET 失败的原因。为什么会失败?【参考方案4】:

我在 Alexander Vasiliev 的回答中遇到了数组校正问题。

我无法评论他的答案(没有足够的声誉点;-)),所以我不得不添加一个新的答案...... (我有一个弹出窗口作为最佳做法,不回答其他答案,只回答原始问题 - bof)

    if (Object.prototype.toString.call(obj) === '[object Array]') 
        for (var i = 0; i < obj.length; i++) 
            // check also if the array element is not a primitive value
            if (typeof obj[i] !== 'object' || !obj[i]) // a primitive value
                return obj[i];
            if ("$ref" in obj[i])
                obj[i] = recurse(obj[i], i, obj);
            else
                obj[i] = recurse(obj[i], prop, obj);
        
        return obj;
    

【讨论】:

但是我不再在生产中使用它,因为最新版本的 Microsoft ASP.NET OData 服务器端实现不支持使用“$ref”输出来引用已返回的对象。微软在其论坛中表示他们不会实施它。 ;-( 数组循环中是否需要区分$ref?无论如何,下一个实例都会进行检查,如果是非参考,我怀疑将“prop”作为第二个参数传递是否正确。【参考方案5】:

在公认的实现中,如果您正在检查一个数组并遇到一个原始值,您将返回该值并覆盖该数组。您希望继续检查数组的所有元素并在最后返回数组。

function resolveReferences(json) 
    if (typeof json === 'string')
        json = JSON.parse(json);

    var byid = , // all objects by id
        refs = []; // references to objects that could not be resolved
    json = (function recurse(obj, prop, parent) 
        if (typeof obj !== 'object' || !obj) // a primitive value
            return obj;
        if (Object.prototype.toString.call(obj) === '[object Array]') 
            for (var i = 0; i < obj.length; i++)
                // check also if the array element is not a primitive value
                if (typeof obj[i] !== 'object' || !obj[i]) // a primitive value
                    continue;
                else if ("$ref" in obj[i])
                    obj[i] = recurse(obj[i], i, obj);
                else
                    obj[i] = recurse(obj[i], prop, obj);
            return obj;
        
        if ("$ref" in obj)  // a reference
            var ref = obj.$ref;
            if (ref in byid)
                return byid[ref];
            // else we have to make it lazy:
            refs.push([parent, prop, ref]);
            return;
         else if ("$id" in obj) 
            var id = obj.$id;
            delete obj.$id;
            if ("$values" in obj) // an array
                obj = obj.$values.map(recurse);
            else // a plain object
                for (var prop in obj)
                    obj[prop] = recurse(obj[prop], prop, obj);
            byid[id] = obj;
        
        return obj;
    )(json); // run it!

    for (var i = 0; i < refs.length; i++)  // resolve previously unknown references
        var ref = refs[i];
        ref[0][ref[1]] = byid[ref[2]];
        // Notice that this throws if you put in a reference at top-level
    
    return json;

【讨论】:

【参考方案6】:

我的解决方案(也适用于数组):

用法:rebuildJsonDotNetObj(jsonDotNetResponse)

代码:

function rebuildJsonDotNetObj(obj) 
    var arr = [];
    buildRefArray(obj, arr);
    return setReferences(obj, arr)


function buildRefArray(obj, arr) 
    if (!obj || obj['$ref'])
        return;
    var objId = obj['$id'];
    if (!objId)
    
        obj['$id'] = "x";
        return;
    
    var id = parseInt(objId);
    var array = obj['$values'];
    if (array && Array.isArray(array)) 
        arr[id] = array;
        array.forEach(function (elem) 
            if (typeof elem === "object")
                buildRefArray(elem, arr);
        );
    
    else 
        arr[id] = obj;
        for (var prop in obj) 
            if (typeof obj[prop] === "object") 
                buildRefArray(obj[prop], arr);
            
        
    


function setReferences(obj, arrRefs) 
    if (!obj)
        return obj;
    var ref = obj['$ref'];
    if (ref)
        return arrRefs[parseInt(ref)];

    if (!obj['$id']) //already visited
        return obj;

    var array = obj['$values'];
    if (array && Array.isArray(array)) 
        for (var i = 0; i < array.length; ++i)
            array[i] = setReferences(array[i], arrRefs)
        return array;
    
    for (var prop in obj)
        if (typeof obj[prop] === "object")
            obj[prop] = setReferences(obj[prop], arrRefs)
    delete obj['$id'];
    return obj;

【讨论】:

+1 这对于System.Text.Json 产生的输出也很有效。我必须按如下方式调用函数rebuildJsonDotNetObj(JSON.parse(myjson)),其中myjson 变量保存JSON 文本。

以上是关于解析来自 JSON 对象的循环引用的主要内容,如果未能解决你的问题,请参考以下文章

json数据避免$ref 循环引用

Json.NET如何避免循环引用

EF中Json序列化对象时检测到循环引用的解决办法

您如何“真正”使用 Newtonsoft.Json 序列化循环引用对象?

EntityFramework中Json序列化的循环引用问题解决--Newtonsoft.Json

iOS之深入解析如何检测“循环引用”