解析来自 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.parse
的reviver
参数,这实际上非常简单。
示例如下。查看浏览器控制台的输出,因为 *** 的 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 对象的循环引用的主要内容,如果未能解决你的问题,请参考以下文章
您如何“真正”使用 Newtonsoft.Json 序列化循环引用对象?