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

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

User:{id:1,{Foo{id:1,prop:1}},
FooList{$ref: "1",Foo{id:2,prop:13}}

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

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

PreserveReferencesHandling = PreserveReferencesHandling.Objects

但这似乎很浪费..

【问题讨论】:

标签: javascript jquery knockout.js json.net


【解决方案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 参数,这实际上非常简单。

示例如下。查看浏览器控制台的输出,因为 StackOverflow 的 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 文本。
    猜你喜欢
    • 2011-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-25
    • 1970-01-01
    • 2017-08-04
    • 2017-10-12
    • 1970-01-01
    相关资源
    最近更新 更多