2

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.

miyav miyav
  • 338
  • 1
  • 5
  • 18
  • 1
    Your question is fairly confusing at the moment because you talk about shallow copies, but then refer to an array deep in the structure. I suggest simplifying your question: A simple object, with an array property, and an explanation of the result you want. – T.J. Crowder Jul 23 '19 at 14:21
  • No, destructuring will give you a reference. AFAIK, there is no way to configure a destructure to shallow clone at the same time. – Davin Tryon Jul 23 '19 at 14:21
  • @DavinTryon and not in short? – miyav miyav Jul 23 '19 at 14:23

4 Answers4

3

You can't create new objects with destructuring, no. You can only pick out values that exist on the source, you can't perform transformations on them. (You can change the variable name you use, but you can't transform the value.) I've often wanted to, but you can't (at least, not at present).

There are various jump-through-the-hoops ways you could do it, but really the simplest is going to be to make a shallow copy of the array separately.

A simpler example:

const obj = {
  foo: "bar",
  array: [1, 2, 3]
};

const {foo} = obj;
const array = obj.array.slice(); // or: = [...obj.array];

obj.array[0] = "one";
console.log(obj.array[0]); // "one"
console.log(array[0]);     // 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
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!

zfrisch
  • 8,474
  • 1
  • 22
  • 34
  • To note that these solution will not preserve the prototype and won't be able to succeed when their interface is checked for. E.g. it didn't work trying to `getCopy` an Event and then trying to re-dispatch with `dispatchEvent(copiedEvent)` (got: `EventTarget.dispatchEvent: Argument 1 does not implement interface Event.` as a `Proxy` is not an `Event`). — In such specific case one can try the solution from https://stackoverflow.com/a/12593036/3088045 – Kamafeather Jul 25 '23 at 20:03
0

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]
0

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!!! :)

Harsh
  • 1
  • 1