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

JavascriptJqueryJsonSerializationStringify

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 - Javascript

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:

https://stackoverflow.com/questions/2303713/how-to-serialize-dom-node-to-json

JSON.stringify can't represent circular references

Solution 4 - Javascript

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 - Javascript

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.

Solution 6 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1012500View Question on Stackoverflow
Solution 1 - JavascripttockerView Answer on Stackoverflow
Solution 2 - JavascriptKamil KiełczewskiView Answer on Stackoverflow
Solution 3 - JavascriptBrandon BooneView Answer on Stackoverflow
Solution 4 - JavascriptnevfView Answer on Stackoverflow
Solution 5 - JavascriptMatt EvansView Answer on Stackoverflow
Solution 6 - JavascriptTimmerzView Answer on Stackoverflow