Serializing object that contains cyclic object value

Loic Duros picture Loic Duros · Feb 21, 2012 · Viewed 132k times · Source

I have an object (parse tree) that contains child nodes which are references to other nodes.

I'd like to serialize this object, using JSON.stringify(), but I get

TypeError: cyclic object value

because of the constructs I mentioned.

How could I work around this? It does not matter to me whether these references to other nodes are represented or not in the serialized object.

On the other hand, removing these properties from the object when they are being created seems tedious and I wouldn't want to make changes to the parser (narcissus).

Answer

georg picture georg · Feb 21, 2012

Use the second parameter of stringify, the replacer function, to exclude already serialized objects:

var seen = [];

JSON.stringify(obj, function(key, val) {
   if (val != null && typeof val == "object") {
        if (seen.indexOf(val) >= 0) {
            return;
        }
        seen.push(val);
    }
    return val;
});

http://jsfiddle.net/mH6cJ/38/

As correctly pointed out in other comments, this code removes every "seen" object, not only "recursive" ones.

For example, for:

a = {x:1};
obj = [a, a];

the result will be incorrect. If your structure is like this, you might want to use Crockford's decycle or this (simpler) function which just replaces recursive references with nulls:

function decycle(obj, stack = []) {
    if (!obj || typeof obj !== 'object')
        return obj;
    
    if (stack.includes(obj))
        return null;

    let s = stack.concat([obj]);

    return Array.isArray(obj)
        ? obj.map(x => decycle(x, s))
        : Object.fromEntries(
            Object.entries(obj)
                .map(([k, v]) => [k, decycle(v, s)]));
}

//

let a = {b: [1, 2, 3]}
a.b.push(a);

console.log(JSON.stringify(decycle(a)))