Stringify (convert to JSON) a JavaScript object with circular reference
Solution 1
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...
Solution 2
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);
Solution 3
It appears that dojo can represent circular references in JSON in the form : {"id":"1","me":{"$ref":"1"}}
Here is an example:
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
Solution 4
I found two suitable modules to handle circular references in JSON.
- 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
- 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.
Solution 5
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.
Related videos on Youtube
user1012500
Updated on February 14, 2022Comments
-
user1012500 about 2 years
I've got a JavaScript object definition which contains a circular reference: it has a property that references the parent object.
It also has functions that I don't want to be passed through to the server. How would I serialize and deserialize these objects?
I've read that the best method to do this is to use Douglas Crockford's stringify. However, I'm getting the following error in Chrome:
TypeError: Converting circular structure to JSON
The code:
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)); } }
This is a test case that I created for this question. There are errors within this code but essentially I have objects within objects, and a reference passed to each object to show what the parent object is when the object is created. Each object also contains functions, which I don't want stringified. I just want the properties such as the
Person.Name
.How do I serialize before sending to the server and deserialize it assuming that the same JSON is passed back?
-
mathheadinclouds over 4 yearsrelated: stackoverflow.com/questions/23117470/… related: stackoverflow.com/questions/2010892/… related: stackoverflow.com/questions/9382167/… related: stackoverflow.com/questions/36716367/…
-
ruffin almost 4 yearsCrockford would have you use his cycle.js as the
replacer
in yourstringify
call as @MattEvans points out, below.
-
-
user1012500 about 12 yearsHi, thanks for your answer. I should have stated that I'm using jquery as my is library. I didn't think it was relevant at the time.ill update my post.
-
Brandon Boone about 12 years@user1012500 - Dojo works fine alongside jQuery. I often include other libraries or frameworks to compensate for deficiencies in my primary framework. You may even be able to extract the
toJson
andfromJson
methods and create your own jQuery wrapper around them. That way you won't need to pull in the whole framework. Unfortunately, jQuery does not have this functionality out of the box, and JSON.stringify can't handle these types of objects. So other than the above examples, you may have to code this functionality yourself. -
user1012500 about 12 yearsHi Brandon, I'm hesitant to add another library to solve the problem since it adds another footprint to the site. However, i gave dojo a shot and attempted to use your example against mine. However, i ran into the circular reference issue (I don't have much knowledge of dojo, so I've just attempted a couple of things but mainly based on your example): jsfiddle.net/Af3d6/1
-
Brandon Boone about 12 yearsJSON not to be confused with JavaScript object literals cannot contain functions since it is a data interchange format (like XML). So in order to serialize to the JSON format, you need a compliant object literal. This example modifies your example to be compliant (though it may not be what your looking for since you're no longer working with object instances). Even when you remove your toJSON functions, the references to the underlying prototype functions still makes it invalid.
-
user1012500 almost 12 yearsHi, In the end I had to go it alone and customise my stringify calls. thanks anyway.
-
Brandon Boone almost 12 years@user1012500 - Hey no problem. Glad you were able to find a solution. For the benefit of others, you should post your own answer and mark it as the correct answer to close out this question.
-
ZachB almost 8 yearsThis will remove instances of
jQuery
andHTMLElement
, not circular references? -
moeiscool about 7 years@ZachB i think in his setup these are circular for him... but the problem is that just because javascript is the language in use doesn't mean we have jquery or even HTML elements.
-
Esqarrouth over 5 yearsWhat should we change the variable
parent
with? -
DJDaveMark about 5 years@Esqarrouth
owner
is usually used instead ofparent
. -
JoaoPauloPaschoal about 5 yearsDojo's ref module managed to stringify / parse my models like a breeze. In my scenario the cycle.js library from crockford wasn't successful because I'm using newtonsoft json.net to serialize C# objects and it doesn't use jsonpath (as cycle.js uses). Looks like the good old dojo saved my day! Thanks for this answer.
-
yzfdjzwl over 2 yearsit's awesome....