JavaScript: Deep Copy Circular JSON

intro:

I'm trying to write a deep copy method, but I need to keep track of my visited nodes so that I can reference earlier visitedNode

instead of deep copying forever before.

attempts:

var visitedNodes = {};
var obj = {}; obj.a = obj;   // circular; can't use JSON.stringify)
var obj2 = {};

visitedNodes[obj] = "should need key obj (not obj2) to access this string";

console.log(visitedNodes[obj2]);    // logs the string unfortunately

      

I have no unique way to store the memory location - it is stored in [object Object]

and I cannot use JSON.stringify because it is a circular structure


I tried to use var visitedNodes = new Map();

but still not dice


My current approach is to use a function Array.prototype.indexOf

, but I don't know if it works with circular structures because I get a stack overflow too!

this.clone = function (item, visitedNodes) {
    visitedNodes = visitedNodes || [];
    if (typeof item === "object" && !Array.isArray(item)) {
        if (visitedNodes.indexOf(item) === -1) {
            var cloneObject = {};
            visitedNodes.push(cloneObject);
            for (var i in item) {
                if (item.hasOwnProperty(i)) {
                    cloneObject[i] = this.clone(item[i], visitedNodes);
                }
            }
            return cloneObject;
        } else {
            return visitedNodes[visitedNodes.indexOf(item)];
        }
    }
    else if (typeof item === "object" && Array.isArray(item)) {
        if (visitedNodes.indexOf(item) === -1) {
            var cloneArray = [];
            visitedNodes.push(cloneArray);
            for (var j = 0; j < item.length; j++) {
                cloneArray.push(this.clone(item[j], visitedNodes));
            }
            return cloneArray;
        } else {
            return visitedNodes[visitedNodes.indexOf(item)];
        }
    }

    return item; // not object, not array, therefore primitive
};

      


question:

Does anyone have any ideas for getting a unique memory address so I can tell if I've been on an Object reference before? I believe I could build a unique hash based on Object.keys()

and Object.prototype.constructor

, but that seems absurd and will give false positives if the constructor is the same and the child keys are the same as the parent keys

+3


source to share


2 answers


In visitNodes we save the original link and create another array to save with the same index, a clone object that will be used when it is a link.

function deepClone(obj) {
    var visitedNodes = [];
    var clonedCopy = [];
    function clone(item) {
        if (typeof item === "object" && !Array.isArray(item)) {
            if (visitedNodes.indexOf(item) === -1) {
                visitedNodes.push(item);
                var cloneObject = {};
                clonedCopy.push(cloneObject);
                for (var i in item) {
                    if (item.hasOwnProperty(i)) {
                        cloneObject[i] = clone(item[i]);
                    }
                }
                return cloneObject;
            } else {
                return clonedCopy[visitedNodes.indexOf(item)];
            }
        }
        else if (typeof item === "object" && Array.isArray(item)) {
            if (visitedNodes.indexOf(item) === -1) {
                var cloneArray = [];
                visitedNodes.push(item);
                clonedCopy.push(cloneArray);
                for (var j = 0; j < item.length; j++) {
                    cloneArray.push(clone(item[j]));
                }
                return cloneArray;
            } else {
                return clonedCopy[visitedNodes.indexOf(item)];
            }
        }

        return item; // not object, not array, therefore primitive
    }
    return clone(obj);
}

var obj = {b: 'hello'};
obj.a = { c: obj };
var dolly = deepClone(obj);
obj.d = 'hello2';
console.log(obj);
console.log(dolly);

      



example of running code: http://jsbin.com/favekexiba/1/watch?js,console

+4


source


The code in Fetz's answer works fine, but breaks up Date objects. Here's the revised version:

const visitedNodes = [];
const clonedCopy = [];

function clone(item) {
  if (typeof item === 'object') {
    if (item instanceof Date) { // Date
      if (visitedNodes.indexOf(item) === -1) {
        visitedNodes.push(item);
        var cloneObject = new Date(item);
        clonedCopy.push(cloneObject);
        return cloneObject;
      }
      return clonedCopy[visitedNodes.indexOf(item)];
    } else if (XMLDocument && item instanceof XMLDocument) { // XML Document
      if (visitedNodes.indexOf(item) === -1) {
        visitedNodes.push(item);
        const cloneObject = item.implementation.createDocument(item.documentElement.namespaceURI, null, null);
        const newNode = cloneObject.importNode(item.documentElement, true);
        cloneObject.appendChild(newNode);
        clonedCopy.push(cloneObject);
        return cloneObject;
      }
      return clonedCopy[visitedNodes.indexOf(item)];
    } else if (!Array.isArray(item)) { // Object
      if (visitedNodes.indexOf(item) === -1) {
        visitedNodes.push(item);
        var cloneObject = {};
        clonedCopy.push(cloneObject);
        for (const i in item) {
          if (item.hasOwnProperty(i)) {
            cloneObject[i] = clone(item[i]);
          }
        }
        return cloneObject;
      }
      return clonedCopy[visitedNodes.indexOf(item)];
    } else if (Array.isArray(item)) { // Array
      if (visitedNodes.indexOf(item) === -1) {
        const cloneArray = [];
        visitedNodes.push(item);
        clonedCopy.push(cloneArray);
        for (let j = 0; j < item.length; j++) {
          cloneArray.push(clone(item[j]));
        }
        return cloneArray;
      }
      return clonedCopy[visitedNodes.indexOf(item)];
    }
  }

  return item; // not date, not object, not array, therefore primitive
}
return clone(obj);

      



I would prefer to edit Fetz's answer, but the edit queue is full.

edit 07/19/2017 : added cloning of XML document

+1


source







All Articles