Javascript Deep Clone Object with Circular References

circular-reference, clone, javascript, node.js

Solution

There is now structuredClone in the Web API which also works with circular references.

Previous answer

I would suggest to use a Map to map objects in the source with their copy in the destination. In fact, I ended up using `WeakMap` as suggested by Bergi. Whenever a source object is in the map, its corresponding copy is returned instead of recursing further.

At the same time some of the code in the original `deepClone` code can be optimised further:

The first part testing for primitive values has a small issue: it treats `new Number(1)` differently from `new Number(2)`. This is because of the `==` in the first `if`. It should be changed to `===`. But really, the first few lines of code seem then equivalent to this test: `Object(obj) !== obj`

I also rewrote some `for` loops into more functional expressions

This needs ES6 support:

function deepClone(obj, hash = new WeakMap()) {
    // Do not try to clone primitives or functions
    if (Object(obj) !== obj || obj instanceof Function) return obj;
    if (hash.has(obj)) return hash.get(obj); // Cyclic reference
    try { // Try to run constructor (without arguments, as we don't know them)
        var result = new obj.constructor();
    } catch(e) { // Constructor failed, create object without running the constructor
        result = Object.create(Object.getPrototypeOf(obj));
    }
    // Optional: support for some standard constructors (extend as desired)
    if (obj instanceof Map)
        Array.from(obj, ([key, val]) => result.set(deepClone(key, hash), 
                                                   deepClone(val, hash)) );
    else if (obj instanceof Set)
        Array.from(obj, (key) => result.add(deepClone(key, hash)) );
    // Register in hash    
    hash.set(obj, result);
    // Clone and assign enumerable own properties recursively
    return Object.assign(result, ...Object.keys(obj).map (
        key => ({ [key]: deepClone(obj[key], hash) }) ));
}
// Sample data
function A() {}
function B() {}
var a = new A();
var b = new B();
a.b = b;
b.a = a;
// Test it
var c = deepClone(a);
console.log('a' in c.b.a.b); // true

Problem

I have copied the function below from an existing answer by Dmitriy Pichugin. This function can deep clone an object without any circular references- it works. ``` function deepClone( obj ) { if( !obj || true == obj ) //this also handles boolean as true and false return obj; var objType = typeof( obj ); if( "number" == objType || "string" == objType ) // add your immutables here return obj; var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor(); if( obj instanceof Map ) for( var key of obj.keys() ) result.set( key, deepClone( obj.get( key ) ) ); for( var key in obj ) if( obj.hasOwnProperty( key ) ) result[key] = deepClone( obj[ key ] ); return result; } ``` However, my program loops indefinitely and I have realised that this is due to a circular reference. An example of a circular reference: ``` function A() {} function B() {} var a = new A(); var b = new B(); a.b = b; b.a = a; ```

Original source

Related problems