Stringify (convert to JSON) a JavaScript object with circular reference

102,670

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:

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

Solution 4

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.

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.

Share:
102,670

Related videos on Youtube

user1012500
Author by

user1012500

Updated on February 14, 2022

Comments

  • user1012500
    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?

  • user1012500
    user1012500 about 12 years
    Hi, 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
    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 and fromJson 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
    user1012500 about 12 years
    Hi 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
    Brandon Boone about 12 years
    JSON 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
    user1012500 almost 12 years
    Hi, In the end I had to go it alone and customise my stringify calls. thanks anyway.
  • Brandon Boone
    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
    ZachB almost 8 years
    This will remove instances of jQuery and HTMLElement, not circular references?
  • moeiscool
    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
    Esqarrouth over 5 years
    What should we change the variable parent with?
  • DJDaveMark
    DJDaveMark about 5 years
    @Esqarrouth owner is usually used instead of parent.
  • JoaoPauloPaschoal
    JoaoPauloPaschoal about 5 years
    Dojo'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
    yzfdjzwl over 2 years
    it's awesome....