'Can I use Destructuring to create a deep copy?

Basically I want to get a shallow deep copy that won't change my main object using destructuring.

let a = {
    name: 'lala', 
  testArray: [1,2,3], 
  object: {
    name: 'object', 
    array: [4,5,6]
  }
};

const {name, testArray, object} =  a;

object.array = [...object.array, 0];
console.log(a.object.array);

let b = {
  object2: {
    name: 'object', 
    array: [4,5,6]
  }
};

const object2 =  {...b.object2};
object2.array = [...object2.array, 0];

console.log(b.object2.array);

I made a jsfiddle (for easier reproduction) providing the code I wrote.

https://jsfiddle.net/5z71Lbja/

The problem is that the array of main object also changes when I change the "child" object using the first method(destructuring). The second method works fine but I'm curious if I can achieve the same result using destructuring.



Solution 1:[1]

It is not possible directly.

let { object } = a;
object = {...object}; // Object.assign({}, object); <<

object.array = [0, ...object.array, 0];

console.log(object.array); // [0, 4, 5, 6, 0]
console.log(a.object.array); // [4, 5, 6]

Solution 2:[2]

Answer

You can achieve this by using a Proxy Object, either through a function, or directly.


Using a Function during Destruct:

The Proxy object takes the target object, and a handler.

A handler allows you to set certain conditions like get and set that can alter the way that data is returned to the user. We'll be using get for this.

In the below handler, we alter the get functionality. We check if the target[prop] returns an Array or Object. If it does, we create a copy in memory and return that instead of the reference. If it is not an Array or Object we simply return the primitive value (string, number, etc)

let copyHandler = {
  get: function( target, prop, receiver ) {
    let value = target[ prop ];
    if ( Array.isArray( value ) ) return value.slice( 0 );
    if ( typeof value === "object" && value.constructor.name === "Object" ) return Object.assign( {}, value );
    return value;
  }
}, getCopy = obj => new Proxy(obj, copyHandler);

Utilizing the getCopy function as our destructuring middle-man, we can be sure that all our values return new references:

const {
  name,
  testArray,
  object
} = getCopy(a);


object.array = [...object.array, 0];
console.log(a.object.array); // [4,5,6]
console.log(object.array); // [4,5,6,0]

Example:

let copyHandler = {
  get: function( target, prop, receiver ) {
    let value = target[ prop ];
    if ( Array.isArray( value ) ) return value.slice( 0 );
    if ( typeof value === "object" && value.constructor.name === "Object" ) return Object.assign( {}, value );
    return value;
  }
}, getCopy = obj => new Proxy(obj, copyHandler);



let a = {
  name: 'lala',
  testArray: [ 1, 2, 3 ],
  object: {
    name: 'object',
    array: [ 4, 5, 6 ]
  }
};

const {
  name,
  testArray,
  object
} = getCopy(a);



object.array = [...object.array, 0];
console.log(a.object.array); // [4,5,6]
console.log(object.array); // [4,5,6,0]

Alternatively, we can do it directly on Declaration/Initialization/Reception:

Directly in this sense means that we can setup the Object to return copies during destructured declaration only.

We do this similarly to above by utilizing a Proxy, and a middle-man function.

Note: The middle-man functions aren't necessary, but it helps keep things organized.

let destructHandler = {
  get: function( target, prop, receiver ) {
    if(!this.received) this.received = new Set();

    let value = target[ prop ];
    if(this.received.has(prop)) return value;

    this.received.add(prop);

    if ( Array.isArray( value ) ) return value.slice( 0 );
    if ( typeof value === "object" && value.constructor.name === "Object" ) return Object.assign( {}, value );
    return value;
  }, destructable = obj => new Proxy(obj, destructHandler);

The difference here is that our get handler uses a Set to determine whether or not a property has already been grabbed once.

It will return copies upon the first request for a referential property (an Array or Object). It will still return any primitive value as normal.

This means that upon declaring/initialization/reception of the object, you can apply the destructable proxy and immediately afterwards pull out copies from that object using a destruct.

Initialization example code:

let a = destructable({
  name: 'lala',
  testArray: [ 1, 2, 3 ],
  object: {
    name: 'object',
    array: [ 4, 5, 6 ]
  }
});

Destructure example:

const {
  name,
  testArray,
  object
} = a;

object.array = [...object.array, 0];
console.log(a.object.array); // [4,5,6]
console.log(object.array); // [4,5,6,0]

Example:

let destructHandler = {
  get: function( target, prop, receiver ) {
    if(!this.received) this.received = new Set();
    
    let value = target[ prop ];
    if(this.received.has(prop)) return value;
    
    this.received.add(prop);
    
    if ( Array.isArray( value ) ) return value.slice( 0 );
    if ( typeof value === "object" && value.constructor.name === "Object" ) return Object.assign( {}, value );
    return value;
  }
}, destructable = obj => new Proxy(obj, destructHandler);


let a = destructable({
  name: 'lala',
  testArray: [ 1, 2, 3 ],
  object: {
    name: 'object',
    array: [ 4, 5, 6 ]
  }
});

const {
  name,
  testArray,
  object
} = a;
    object.array = [...object.array, 0];
    console.log(object.array); // [4,5,6,0]
    console.log(a.object.array); // [4,5,6]

Hope this helps! Happy Coding!

Solution 3:[3]

I think this following code can be tried out for make a deep copy of an object, I used Destructuring.

function deepcopy(obj) {
  let { ...data
  } = obj;
  let newObj = { ...data
  }

  // deleting one key (just to show that it doesn't affect original obj)
  delete newObj.a


  console.log("param obj ", obj);
  return newObj;
}
const obj = {
  a: 1,
  b: 2,
  c: 3
}
console.log("original obj ", obj);
console.log("copy newObj", deepcopy(obj));
console.log("original obj ", obj);

Hope this helps!!! :)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Victor Taek Lim
Solution 2 zfrisch
Solution 3 Harsh