How to deep clone a nested object indexed by Symbol() keys?

163 views Asked by At

For usual object using string as key, we can use JSON.parse(JSON.stringify(myObject)). Another How can I deep clone an object using Symbol() as keys ?

Note: structuredClone({[Symbol()]: 42}) returns {} which is not usable either

2

There are 2 answers

2
Alexander Nenashev On

Use Object.getOwnPropertySymbols to iterate and copy symbols.

You can improve the code by copying property descriptors instead of simple property assignment.

enter image description here

const symbol = Symbol();
const symbol2 = Symbol();
const obj = {[symbol]: 42, child: {[symbol2]: 11}, arr: [{[Symbol()]: 'foo'}, 2, 3]};

const cloneObjectWithSymbols = (obj) => {

  if(obj?.constructor?.name === 'Object'){
    
    const out = {};
    
    for(const symbol of Object.getOwnPropertySymbols(obj)){
      const prop = Object.getOwnPropertyDescriptor(obj, symbol);
      Object.defineProperty(out, symbol, prop);
    }
    
    for(const k in obj){
      out[k] = cloneObjectWithSymbols(obj[k]);
    }
    return out;
  
  } 
  
  return Array.isArray(obj) ? obj.map(cloneObjectWithSymbols) : obj;

};

const cloned = cloneObjectWithSymbols(obj);
console.log(cloned[symbol], cloned.child[symbol2]);
console.log(cloned);

0
mandy8055 On

One approach would be to iterate through all properties and Symbols and copy them to a new object(since structuredClone will not be able to clone Symbols and methods1).

Pseudocode:
# 1. Base case of recursive call: if provided obj is null or not an object return the value as is.
# 2. initialize cloned object as object or array based on obj type.
# 3. for(all the own property keys of the input obj)
    # 3.1. if current prop is an object and !null apply recursive call. why? since it is nested.
    # 3.2. else it will be some primitive value which suggest that we define a new property on clonedObj with its descriptors and value.
# 4. return clonedObj
function customDeepCloneWithSymbols(obj) {
    // Base Case
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
    
    let clonedObj = Array.isArray(obj) ? [] : {};
    // Faith
    for (let prop of Reflect.ownKeys(obj)) {
        if (typeof obj[prop] === 'object' && obj[prop] !== null) {
            clonedObj[prop] = customDeepCloneWithSymbols(obj[prop]);
        } else {
            Reflect.defineProperty(clonedObj, prop, Reflect.getOwnPropertyDescriptor(obj, prop));
          // or
          // Object.defineProperty(clonedObj, prop, Object.getOwnPropertyDescriptor(obj, prop));
        }
    }

    return clonedObj;
}

SIDENOTE:

The clone and cloneDeep functions provided by lodash clone Symbols in case you're flexible for using a library to achieve your purpose.