从JSON对象解析循环引用

时间:2022-08-23 00:23:46

If I have a serialized JSON from json.net like so:

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

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

I want to have knockout output a foreach over FooList but I am not sure how to proceed because the $ref things could throw things.

我想让淘汰输出超过FooList,但我不知道如何继续,因为$ ref的东西可能会抛出东西。

I'm thinking the solution would be to somehow force all the Foos to be rendered in the FooList by not using:

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

PreserveReferencesHandling = PreserveReferencesHandling.Objects

but that seems wasteful..

但这似乎很浪费..

6 个解决方案

#1


13  

The json object which you are receiving from the server contains Circular References. Before using the object you should have to first remove all the $ref properties from the object, means in place of $ref : "1" you have to put the object which this link points.

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

In your case may be it is pointing to the User's object whose id is 1

在你的情况下,它可能指向id为1的User对象

For this you should check out Douglas Crockfords Plugin on github.There is a cycle.js which can do the job for you.

为此你应该看看github上的Douglas Crockfords插件。有一个cycle.js可以为你完成这项工作。

or you can use the following code (not tested) :

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

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

Let me know if it helps !

如果有帮助请告诉我!

#2


23  

I've found some bugs and implemented arrays support:

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

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

#3


3  

I had trouble with the array correction in the answer of Alexander Vasiliev.

在亚历山大·瓦西里耶夫的回答中,我在阵列校正方面遇到了麻烦。

I can't comment his answer (don't own enough reputations points ;-) ), so I had to add a new answer... (where I had a popup as best practice not to answer on other answers and only on the original question - bof)

我不能评论他的答案(没有足够的声誉点;-)),所以我不得不添加一个新的答案...(我有一个弹出窗口作为最佳做法不回答其他答案,只有在原始问题 - 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;
    }

#4


2  

In the accepted implementation, if you're inspecting an array and come across a primitive value, you will return that value and overwrite that array. You want to instead continue inspecting all of the elements of the array and return the array at the end.

在接受的实现中,如果您正在检查数组并遇到原始值,则将返回该值并覆盖该数组。您希望继续检查数组的所有元素并在结尾返回数组。

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

#5


2  

This is actually extremely simple if you take advantage of JSON.parse's reviver parameter:

如果你利用JSON.parse的reviver参数,这实际上非常简单:

// 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));

#6


0  

my solution(works for arrays as well):

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

usage: rebuildJsonDotNetObj(jsonDotNetResponse)

The code:

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


13  

The json object which you are receiving from the server contains Circular References. Before using the object you should have to first remove all the $ref properties from the object, means in place of $ref : "1" you have to put the object which this link points.

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

In your case may be it is pointing to the User's object whose id is 1

在你的情况下,它可能指向id为1的User对象

For this you should check out Douglas Crockfords Plugin on github.There is a cycle.js which can do the job for you.

为此你应该看看github上的Douglas Crockfords插件。有一个cycle.js可以为你完成这项工作。

or you can use the following code (not tested) :

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

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

Let me know if it helps !

如果有帮助请告诉我!

#2


23  

I've found some bugs and implemented arrays support:

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

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

#3


3  

I had trouble with the array correction in the answer of Alexander Vasiliev.

在亚历山大·瓦西里耶夫的回答中,我在阵列校正方面遇到了麻烦。

I can't comment his answer (don't own enough reputations points ;-) ), so I had to add a new answer... (where I had a popup as best practice not to answer on other answers and only on the original question - bof)

我不能评论他的答案(没有足够的声誉点;-)),所以我不得不添加一个新的答案...(我有一个弹出窗口作为最佳做法不回答其他答案,只有在原始问题 - 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;
    }

#4


2  

In the accepted implementation, if you're inspecting an array and come across a primitive value, you will return that value and overwrite that array. You want to instead continue inspecting all of the elements of the array and return the array at the end.

在接受的实现中,如果您正在检查数组并遇到原始值,则将返回该值并覆盖该数组。您希望继续检查数组的所有元素并在结尾返回数组。

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

#5


2  

This is actually extremely simple if you take advantage of JSON.parse's reviver parameter:

如果你利用JSON.parse的reviver参数,这实际上非常简单:

// 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));

#6


0  

my solution(works for arrays as well):

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

usage: rebuildJsonDotNetObj(jsonDotNetResponse)

The code:

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