Copying objects in JavaScript
Tags: javascript, typescript, react, angular
Published: 2020-06-06 17:56:09

#####Scenario:
Whenever we pass objects between components as props or as arguments in function, we need to copy that object to make sure that it doesn't affect the original one. Now we can easily make any changes to the copied object to our need.

So, following are the methods that we can use to do so:

- Spread operator
- Object.assign
- JSON.parse and JSON.stringify

Does using any of these methods entirely copy the object? Will it also copy a nested object?


Let's look at another example:
```javascript
let deepObj = {a: 4: b: {name: 'react'}, d: {name: 'angular'}};
```


So `deepObj` is a nested object and when it comes to copying nested objects i.e. objects with values as references, there comes the concept of shallow copy and deep copy.

- __Shallow copy__: Only copies one level meaning if any of the value is a reference type then copy the reference but the exact value is not copied in the new object.

- __Deep copy__: Copy every level of nested values even if it is a reference type like our example with `deepObj` object above.

*__Let's go one by to try copying the objects:__*
-------------------------------------------------------------
**1. Spread Operator & Object.assign():**

*Example 1:*
```javascript
let obj1 = {a: 3, b: 4, c: "react"}, copiedObj1 = {};

copiedObj1 = {...obj1};
copiedObj1.c = "angular";

console.log(copiedObj1, obj1); 
{a: 3, b: 4, c: "angular"}
{a: 3, b: 4, c: "react"}



let obj2 = {a: 3, b: 4, c: "react"}, copiedObj2 = {};

copiedObj2 = Object.assign({}, obj2);
copiedObj2.c = "vue";

console.log(copiedObj2, obj2); 
{a: 3, b: 4, c: "vue"}
{a: 3, b: 4, c: "react"}
```
Both will perfectly copy the object because there is no reference type in object values and if you try to change any property that will not have any effect on copied object.

*Example2:*
```javascript
let obj1 = {a: 3, c: [1,2]}, newObj1 = {};
newObj1 = {...obj1};
newObj1.c.push(5);

console.log(newobj1, obj1); 
{a: 3, c: [1,2,5]} 
{a: 3, c: [1,2,5]}


let obj2 = {a: 3, c: [1,2]}, newObj2 = {};
newObj2 = Object.assign({}, obj2);
newObj2.c.push(5);

console.log(newobj2, obj2); 
{a: 3, c: [1,2,5]}
{a: 3, c: [1,2,5]}
```
Here property c value is changes to [1,2,5] in both objects, so this is not perfectly copied because of reference type i.e. array ([1,2]). It just copies the reference to the array. Hence `Spread operator and Object.assign() only does shallow copying not deep copying.`


**2. JSON.parse() and JSON.stringify():**
```javascript
var obj1 = {a: 3, b: 4, c: "react"};
var copiedObj1 = JSON.parse(JSON.stringify(obj1));
copiedObj1.c = "node";

console.log(copiedObj1, obj1); 
{a: 3, b: 4, c: "node"}
{a: 3, b: 4, c: "react"}


var obj2 = {a: 3, c: [1,2]};
var copiedObj2 = JSON.parse(JSON.stringify(obj2));
copiedObj2.c.push(5);

console.log(copiedObj2 , obj2);
{a: 3, c: [1,2,5]} 
{a: 3, c: [1,2]}
```
This perfectly copies the object as any change in copied object in both case doesn't have any effect in original object.

> But this works only in those cases where value is converted to string first and it gets parsed again.
Following are few of the cases where it fails to copy the object.

```javascript
let obj = {
  name: 'laptop',
  value: function () {
    return 100000';
  }
}

let copiedObj = JSON.parse(JSON.stringify(obj));

console.log(copiedObj);
{name: 'laptop'}
```
Failed -> It removed the value method from copied object.
```javascript
let obj = {a: undefined, b: new Date()}

let copiedObj = JSON.parse(JSON.stringify(obj));

console.log(copiedObj);
{b: "2020-06-06T16:23:43.910Z"}
```
Failed -> Removed the first property and converted the date value to string;


--------------------------------------------------------------------

###For Shallow copying use
1. Spread operator
2. Object.assign().


### For Deep copying
1. Use lodash library `cloneDeep` method (_.cloneDeep(any nested object))

2. Make a custom function which will handle reference types like in below example covering just one case.
```javascript
function deepCopy(obj) {
    let copiedObj = {};
    for(key in obj) {
        if(Array.isArray(obj[key])) {
            copiedObj[key] = [...obj[key]];
        } else {
            copiedObj[key] = obj[key]
        }
    }
    return copiedObj;
}

var obj = {value1: 5, value2: [1,2,3]};

var copiedObj = deepCopy(obj);
copiedObj.value2.push(5);

console.log(copiedObj , obj);
{value1: 5, value2: [1,2,3,5]}
{value1: 5, value2: [1,2,3]}
```
> Better way will be to use recursion to handle reference type

So there are other libraries exists which provide good performance event to do deep cloning as you saw it need more cumputation or you can make a custom function and add more edge case.


Conclusion: Always watch for values if any is reference types in the object that is being passed around. And use shallow and deep copying better.

Thanks for reading!