使用循环引用将 JavaScript 对象 Stringify (转换为 JSON)

我得到了一个包含循环引用的 JavaScript 对象定义: 它有一个引用父对象的属性。

它还有一些我不想传递给服务器的函数。如何序列化和反序列化这些对象?

我读到过最好的方法就是使用道格拉斯·克罗克福特的 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,在发送到服务器之前如何序列化并反序列化它?

107886 次浏览

It appears that dojo can represent circular references in JSON in the form : {"id":"1","me":{"$ref":"1"}}

Here is an example:

http://jsfiddle.net/dumeG/

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

Produces:

{
"name":"Kris",
"father":{
"name":"Bill",
"wife":{
"name":"Karen"
}
},
"mother":{
"$ref":"#father.wife"
}
}

Note: You can also de-serialize these circular referenced objects using the dojox.json.ref.fromJson method.

Other Resources:

How to serialize DOM node to JSON even if there are circular references?

JSON.stringify can't represent circular references

Circular structure error occurs when you have a property of the object which is the object itself directly (a -> a) or indirectly (a -> b -> a).

To avoid the error message, tell JSON.stringify what to do when it encounters a circular reference. For example, if you have a person pointing to another person ("parent"), which may (or may not) point to the original person, do the following:

JSON.stringify( that.person, function( key, value) {
if( key == 'parent') { return value.id;}
else {return value;}
})

The second parameter to stringify is a filter function. Here it simply converts the referred object to its ID, but you are free to do whatever you like to break the circular reference.

You can test the above code with the following:

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

BTW, I'd choose a different attribute name to "parent" since it is a reserved word in many languages (and in DOM). This tends to cause confusion down the road...

I found two suitable modules to handle circular references in JSON.

  1. CircularJSON https://github.com/WebReflection/circular-json whose output can be used as input to .parse(). It also works in Browsers & Node.js Also see: http://webreflection.blogspot.com.au/2013/03/solving-cycles-recursions-and-circulars.html
  2. Isaacs json-stringify-safe https://github.com/isaacs/json-stringify-safe which maybe more readable but can't be used for .parse and is only available for Node.js

Either of these should meet your needs.

I used the following to eliminate the circular references:

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

Happened upon this thread because I needed to log complex objects to a page, since remote debugging wasn't possible in my particular situation. Found Douglas Crockford's (inceptor of JSON) own cycle.js, which annotates circular references as strings such that they can be reconnected after parsing. The de-cycled deep copy is safe to pass through JSON.stringify. Enjoy!

https://github.com/douglascrockford/JSON-js

cycle.js: This file contains two functions, JSON.decycle and JSON.retrocycle, which make it possible to encode cyclical structures and dags in JSON, and to then recover them. This is a capability that is not provided by ES5. JSONPath is used to represent the links.

No-lib

Use below replacer to generate json with string references (similar to json-path) to duplicate/circular referenced objects

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

And following parser function to regenerate object from such "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);