Skip to content

Instantly share code, notes, and snippets.

@lukakostic
Last active May 22, 2024 18:45
Show Gist options
  • Save lukakostic/582b664b6081581b6eb5c39ad814026f to your computer and use it in GitHub Desktop.
Save lukakostic/582b664b6081581b6eb5c39ad814026f to your computer and use it in GitHub Desktop.
JS Circular reference DeepCopy and LinearizedDeepCopy (can be jsoned and back)
/*
DeepCopy and LinearizedDeepCopy which can handle circular references.
LinearizedDeepCopy can be jsonified and back yet keep circular references.
(For use see comments below the functions)
// these dont clone functions or keep class(constructor) info!!!
// so its like JSON.parse(JSON.stringify(obj)) but faster(?) and can do circular references.
*/
function deepCopy(obj, map = undefined, reverseMap = undefined) {
if (map === undefined) {
map = new WeakMap();
reverseMap = new WeakMap();
}
function register(original, clone) {
map.set(original, clone);
reverseMap.set(clone, original);
return clone;
}
let clone = map.get(obj); //clone obj (from map)
if (clone !== undefined) return clone; //already processed
if (reverseMap.get(obj) !== undefined) return obj; //obj is already my clone
let cpy = (k, from = obj) => clone[k] = deepCopy(from[k], map, reverseMap);
if (Array.isArray(obj)) {
let props = Object.getOwnPropertyNames(obj);
clone = register(obj, [...obj]);
for (let i = 0; i < obj.length; i++) {
let idx = props.indexOf(i.toString());
props.slice(idx, idx + 1); //remove processed
cpy(i);
}
props.forEach(p => cpy(p)); //process unprocessed
//have to handle the indexes (<length)
//as well as user-added properties to array object.
} else if (typeof obj == 'object') {
clone = register(obj, {});
for (var k in obj) cpy(k);
} else {
clone = obj; //copy
}
return clone;
}
function linearDeepCopy(obj, map = undefined, reverseMap = undefined, indexedObjs = undefined) {
if (map === undefined) {
map = new WeakMap();
reverseMap = new WeakMap();
indexedObjs = [];
}
function register(original, clone) {
let idx = indexedObjs.push(clone) - 1;
let idxObj = { "!objRefIdx!": idx }; // unique name so we cant mistake it for user objects
map.set(original, idxObj);
reverseMap.set(clone, original);
return [clone, idxObj];
}
let clone = map.get(obj); //clone obj (from map)
if (clone !== undefined) return [clone, indexedObjs]; //already processed
if (reverseMap.get(obj) !== undefined) return [obj, indexedObjs]; //obj is already my clone
let idxClone = null; // {"!objRefIdx!":<idx>} object: replacement for user object.
let cpy = (k, from = obj) => (clone[k] = (linearDeepCopy(from[k], map, reverseMap, indexedObjs)[0]));
if (Array.isArray(obj)) {
let props = Object.getOwnPropertyNames(obj);
[clone, idxClone] = register(obj, [...obj]);
for (let i = 0; i < obj.length; i++) {
let idx = props.indexOf(i.toString());
props.slice(idx, idx + 1); //remove processed
cpy(i);
}
props.forEach(p => cpy(p)); //process unprocessed
//have to handle the indexes (<length)
//as well as user-added properties to array object.
} else if (typeof obj == 'object') {
[clone, idxClone] = register(obj, {});
for (var k in obj) cpy(k);
} else {
clone = obj; //copy
return [clone, indexedObjs];
}
return [idxClone, indexedObjs];
}
function unlinearize(obj, niz, inplace = true, proccessedMap = undefined) {
if (inplace == false) niz = deepCopy(niz); //so we dont edit it.
if (niz.length == 0) return obj; // obj is primitive or doesnt reference other objects
if (typeof obj != 'object') return obj; //is primitive
if (proccessedMap === undefined) proccessedMap = new WeakMap();
if (obj["!objRefIdx!"] !== undefined) obj = niz[obj["!objRefIdx!"]];
if (inplace == false) obj = deepCopy(obj); //assumption: only initial call of unlinearize can have inplace=false
if (proccessedMap.get(obj)) return obj; //so we dont infinitely process circular references
proccessedMap.set(obj, true);
for (var k in obj) {
obj[k] = unlinearize(obj[k], niz, true, proccessedMap);
}
return obj;
}
/*
/////////////////////////////////
/// Deep copy test:
let o = {a:"Test"};
let o2 = {a:o};
o.z = o2; //circular
let orig = {x:o,y:o2,z:["str",o,o2]};
let cpy = (deepCopy(
orig
));
cpy.z[2].a.a = "NOT test anymore.";
console.log(orig);
console.log(cpy);
/////////////////////////////////
*/
/*
/////////////////////////////////
/// Linearized deep copy test:
let o = {a:"Test"};
let o2 = {a:o};
o.z = o2; //circular
let cpy = (linearDeepCopy(
{x:o,y:o2,z:["str",o,o2]}
));
let json = JSON.stringify(cpy);
cpy = JSON.parse(json);
// inplace=false example:
let cpy2 = unlinearize(...cpy,false); //inplace=false so we dont modify cpy[1]
cpy2.z[2].a.a = "NOT test anymore.";
console.log(json==JSON.stringify(cpy)); //true, unmodified due to inplace=false
console.log(cpy2); // correct circular changes.
// inplace=true would be like so:
cpy = unlinearize(...cpy);
cpy.z[2].a.a = "NOT test anymore2.";
console.log(cpy);
/////////////////////////////////
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment