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

IT技术 javascript jquery knockout.js json.net
2021-01-27 10:59:48

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

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

我想在 FooList 上输出一个 foreach,但我不确定如何继续,因为 $ref 的东西可能会抛出东西。

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

PreserveReferencesHandling = PreserveReferencesHandling.Objects

但这似乎很浪费..

6个回答

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

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;
}
在数组处理中仍然存在一个错误,如果它是原始的 ::::code:::: if (typeof obj[i] !== 'object' || !obj[i]) return obj,则不应返回任何内容[一世];
2021-03-22 10:59:48
- 添加 if (Object.prototype.toString.call(obj) === '[object Array]') { ... } - 几乎最新的字符串有错误:ref[0][ref[1]] = byid[参考文献[2]];但必须是:ref[0][ref[1]] = byid[ref[2]]; - 这个字符串是: obj[prop] = recurse(obj[prop], prop, obj) 并变成: obj[prop] = recurse(obj[prop], prop, obj);
2021-03-26 10:59:48
正是我要找的
2021-03-29 10:59:48
多谢!我花了很多时间来搜索错误!
2021-04-01 10:59:48
这可以与“嵌套”数组一起使用吗?我试图没有运气。
2021-04-02 10:59:48

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

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

为此,您应该查看github 上的 Douglas Crockfords 插件。有一个 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 数组中的条目会少得多。在我的对象图中,我什么都没有。
2021-03-17 10:59:48

如果您利用JSON.parse'sreviver参数,这实际上非常简单

下面举例。有关输出,请参阅浏览器控制台,因为 StackOverflow 的代码段控制台输出不会提供结果的准确图片。

// 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)”解决这个问题:-)
2021-03-14 10:59:48
这个很棒!
2021-03-15 10:59:48
您将如何使其适合从 http get(例如来自 REST 服务)获取回复?如果那样的话,get 会失败并且不会调用typescript代码。
2021-03-22 10:59:48
@BoppityBop 我认为答案取决于 GET 失败的原因。为什么会失败?
2021-03-29 10:59:48
@Arcord 感谢您弄清楚并告诉我!答案已更新。
2021-03-31 10:59:48

我在 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;
    }
是否有必要在数组循环中进行 $ref 区分?无论如何,下一个实例都会进行检查,并且在非引用的情况下,我怀疑将“prop”作为第二个参数传递是否正确。
2021-03-19 10:59:48
但是我不再在生产中使用它,因为最新版本的 Microsoft ASP.NET OData 服务器端实现不支持带有“$ref”的输出来引用已经返回的对象。微软在其论坛上表示他们不会实施它。;-(
2021-04-12 10:59:48

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

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;
}