我有一个包含循环引用的JavaScript对象定义:它有一个引用父对象的属性。
它还具有我不想传递给服务器的功能。我如何序列化和反序列化这些对象?
我读过这样做的最佳方法是使用Douglas Crockford的stringify。但是,我在Chrome中收到以下错误:
TypeError:将循环结构转换为JSON
代码:
function finger(xid, xparent){
this.id = xid;
this.xparent;
//other attributes
}
function arm(xid, xparent){
this.id = xid;
this.parent = xparent;
this.fingers = [];
//other attributes
this.moveArm = function() {
//moveArm function details - not included in this testcase
alert("moveArm Executed");
}
}
function person(xid, xparent, xname){
this.id = xid;
this.parent = xparent;
this.name = xname
this.arms = []
this.createArms = function () {
this.arms[this.arms.length] = new arm(this.id, this);
}
}
function group(xid, xparent){
this.id = xid;
this.parent = xparent;
this.people = [];
that = this;
this.createPerson = function () {
this.people[this.people.length] = new person(this.people.length, this, "someName");
//other commands
}
this.saveGroup = function () {
alert(JSON.stringify(that.people));
}
}
这是我为此问题创建的测试用例。此代码中存在错误,但实际上我在对象中有对象,并且传递给每个对象以显示创建对象时父对象的内容。每个对象还包含我不想要字符串化的函数。我只想要Person.Name
。
在发送到服务器之前如何序列化并在假设传回相同的JSON的情况下反序列化它?
答案 0 :(得分:101)
a -> a
)或间接(a -> b -> a
),则会发生循环结构错误。
要避免出现错误消息,请告诉JSON.stringify遇到循环引用时要执行的操作。 例如,如果您有一个人指向可能(或可能不)指向原始人的另一个人(“父母”),请执行以下操作:
JSON.stringify( that.person, function( key, value) {
if( key == 'parent') { return value.id;}
else {return value;}
})
stringify
的第二个参数是过滤器功能。在这里它只是将引用的对象转换为它的ID,但你可以自由地做任何你喜欢的事情来打破循环引用。
您可以使用以下代码测试上述代码:
function Person( params) {
this.id = params['id'];
this.name = params['name'];
this.father = null;
this.fingers = [];
// etc.
}
var me = new Person({ id: 1, name: 'Luke'});
var him = new Person( { id:2, name: 'Darth Vader'});
me.father = him;
JSON.stringify(me); // so far so good
him.father = me; // time travel assumed :-)
JSON.stringify(me); // "TypeError: Converting circular structure to JSON"
// But this should do the job:
JSON.stringify(me, function( key, value) {
if(key == 'father') {
return value.id;
} else {
return value;
};
});
顺便说一句,我选择了一个不同的属性名称“parent
”,因为它是许多语言(以及DOM)中的保留字。这往往会引起混乱......
答案 1 :(得分:10)
dojo似乎可以用以下格式表示JSON中的循环引用:{"id":"1","me":{"$ref":"1"}}
以下是一个例子:
require(["dojox/json/ref"], function(){
var me = {
name:"Kris",
father:{name:"Bill"},
mother:{name:"Karen"}
};
me.father.wife = me.mother;
var jsonMe = dojox.json.ref.toJson(me); // serialize me
alert(jsonMe);
});
制作:
{
"name":"Kris",
"father":{
"name":"Bill",
"wife":{
"name":"Karen"
}
},
"mother":{
"$ref":"#father.wife"
}
}
注意:您也可以使用dojox.json.ref.fromJson
方法对这些循环引用的对象进行反序列化。
其他资源:
How to serialize DOM node to JSON even if there are circular references?
答案 2 :(得分:5)
我找到了两个合适的模块来处理JSON中的循环引用。
其中任何一个都应该满足您的需求。
答案 3 :(得分:3)
在replacer下使用以生成具有字符串引用(类似于json-path)的json,以复制/循环引用对象
let s = JSON.stringify(obj, refReplacer());
function refReplacer() {
let m = new Map(), v= new Map(), init = null;
return function(field, value) {
let p= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);
let isComplex= value===Object(value)
if (isComplex) m.set(value, p);
let pp = v.get(value)||'';
let path = p.replace(/undefined\.\.?/,'');
let val = pp ? `#REF:${pp[0]=='[' ? '$':'$.'}${pp}` : value;
!init ? (init=value) : (val===init ? val="#REF:$" : 0);
if(!pp && isComplex) v.set(value, path);
return val;
}
}
// ---------------
// TEST
// ---------------
// gen obj with duplicate references
let a = { a1: 1, a2: 2 };
let b = { b1: 3, b2: "4" };
let obj = { o1: { o2: a }, b, a }; // duplicate reference
a.a3 = [1,2,b]; // circular reference
b.b3 = a; // circular reference
let s = JSON.stringify(obj, refReplacer(), 4);
console.log(s);
然后使用解析器功能从此类“ ref-json”重新生成对象
function parseRefJSON(json) {
let objToPath = new Map();
let pathToObj = new Map();
let o = JSON.parse(json);
let traverse = (parent, field) => {
let obj = parent;
let path = '#REF:$';
if (field !== undefined) {
obj = parent[field];
path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field?'.'+field:''}`);
}
objToPath.set(obj, path);
pathToObj.set(path, obj);
let ref = pathToObj.get(obj);
if (ref) parent[field] = ref;
for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
}
traverse(o);
return o;
}
// ------------
// TEST
// ------------
let s = `{
"o1": {
"o2": {
"a1": 1,
"a2": 2,
"a3": [
1,
2,
{
"b1": 3,
"b2": "4",
"b3": "#REF:$.o1.o2"
}
]
}
},
"b": "#REF:$.o1.o2.a3[2]",
"a": "#REF:$.o1.o2"
}`;
console.log('Open Chrome console to see nested fields:');
let obj = parseRefJSON(s);
console.log(obj);
答案 4 :(得分:2)
发生在这个线程上,因为我需要将复杂对象记录到页面,因为在我的特定情况下远程调试是不可能的。发现Douglas Crockford(JSON的inceptor)拥有cycle.js,它将循环引用注释为字符串,以便在解析后可以重新连接它们。解除循环的深层副本可以安全地通过JSON.stringify。享受!
https://github.com/douglascrockford/JSON-js
cycle.js:这个文件包含两个函数,JSON.decycle和 JSON.retrocycle,可以编码循环结构 和JSON中的dag,然后恢复它们。这是一种能力 ES5不提供。 JSONPath用于表示链接。
答案 5 :(得分:0)
我改进了Douglas Crockford's solution(使其速度提高了很多),并将代码发布为another question here on SO的答案
答案 6 :(得分:-12)
我使用以下内容来消除循环引用:
JS.dropClasses = function(o) {
for (var p in o) {
if (o[p] instanceof jQuery || o[p] instanceof HTMLElement) {
o[p] = null;
}
else if (typeof o[p] == 'object' )
JS.dropClasses(o[p]);
}
};
JSON.stringify(JS.dropClasses(e));