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
source to share
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
source to share
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
source to share