JavaScript: Using the spread operator with nested objects
Tags: javascript, programming, tutorial, beginners
Published: 2022-02-14 16:48:33

Wouldn't it be nice if copying objects were as simple as reassigning them to new objects?

```javascript
const object = {
  One_A: true,
  One_B: true,
  One_C: {
    Two_A: true,
    Two_B: {
      Three_A: true,
    },
  },
}

const newObject = object
newObject.One_C.Two_B.Three_A = false

console.log("newObject: ", newObject)
console.log("object: ", object)

```
Assigning `object` to `newObject` will give us these results in the console:

```
OUTPUT:

newObject:  {
  One_A: true,
  One_B: true,
  One_C: { Two_A: true, Two_B: { Three_A: false } }
}
object:  {
  One_A: true,
  One_B: true,
  One_C: { Two_A: true, Two_B: { Three_A: false } }
}
```
Changing a value at any level in `newObject` changed the value in `object`.  Why?  Objects in Javascript are passed by reference, not value. The top-level object and each nested object of `newObject` share the exact same locations in memory to those of `object`.  Passing by reference means you are assigning the address location to `newObject`. Make a change in `newObject`, and you change `object`.

Fortunately, the spread operator `...` can be used to make a true copy of `object`, one that can't be altered by changes to the copy.

Well, it's actually not QUITE that simple, I'm afraid. Let's see why.

```javascript
const object = {
  One_A: true,
  One_B: true,
  One_C: {
    Two_A: true,
    Two_B: {
      Three_A: true,
    },
  },
}

const newObject = { ...object }
newObject.One_A = false
newObject.One_B = false
newObject.One_C.Two_A = false
newObject.One_C.Two_B.Three_A = false

console.log("newObject: ", newObject)
console.log("object: ", object)
```
And the result:
```
newObject:  {
  One_A: false,
  One_B: false,
  One_C: { Two_A: false, Two_B: { Three_A: false } }
}
object:  {
  One_A: true,
  One_B: true,
  One_C: { Two_A: false, Two_B: { Three_A: false } }
}
```
Ok, we did pretty well with not changing the top-level elements, but unfortunately, the nested objects at level 2 and 3 WERE changed.

The reason is this:

The spread operator only creates a new address location for the top-level elements.  Any nested objects of `newObject` are still at the same address locations as the nested objects of `object`.

This means that we need to apply the spread operator at every level we want make a true value copy.  So what would that look like?

```javascript
const object = {
  One_A: true,
  One_B: true,
  One_C: {
    Two_A: true,
    Two_B: {
      Three_A: true,
    },
  },
}

const newObject = { ...object, One_C: { ...object.One_C } }
newObject.One_A = false
newObject.One_B = false
newObject.One_C.Two_A = false
newObject.One_C.Two_B.Three_A = false

console.log("newObject: ", newObject)
console.log("object: ", object)
```
The output:
```
newObject:  {
  One_A: false,
  One_B: false,
  One_C: { Two_A: false, Two_B: { Three_A: false } }
}
object:  {
  One_A: true,
  One_B: true,
  One_C: { Two_A: true, Two_B: { Three_A: false } }
}
```
This is better - we managed to protect level 2 of `object`, but we still have to protect the level 3 nested object. And this is what that looks like:

```javascript
const object = {
  One_A: true,
  One_B: true,
  One_C: {
    Two_A: true,
    Two_B: {
      Three_A: true,
    },
  },
}

const newObject = {
  ...object,
  One_C: { ...object.One_C, Two_B: { ...object.One_C.Two_B } },
}
newObject.One_A = false
newObject.One_B = false
newObject.One_C.Two_A = false
newObject.One_C.Two_B.Three_A = false

console.log("newObject: ", newObject)
console.log("object: ", object)
```
And FINALLY:
```
newObject:  {
  One_A: false,
  One_B: false,
  One_C: { Two_A: false, Two_B: { Three_A: false } }
}
object:  {
  One_A: true,
  One_B: true,
  One_C: { Two_A: true, Two_B: { Three_A: true } }  
}
```
As you can see, it starts to get quite messy the more levels of nested objects you have.  Fortunately, there are several JavaScript libraries such as [Immer](https://immerjs.github.io/immer/) that make deep-cloning nested objects fairly intuitive.  But if you find yourself having to rely on only using the spread operator, going step-by-step through the layers is the best way to avoid mistakes.  Ask yourself:

First, which objects do I want to protect? 

```javascript
object, One_C:, Two_B:
```
Next, set up the nesting structure with curly braces:

```javascript
{ object, One_C: { , Two_B: {} } }
```
Finally add the spread operators, making sure you access each object:

```javascript
const newObject = {
  ...object,
  One_C: { ...object.One_C, Two_B: { ...object.One_C.Two_B } }
}
```
Remember that the top level is protected by the spread operator.  `...object` protects level 1, `...object.One_C` protects level 2, and `...object.One_C.Two_B` protects level 3. 

Understanding how to deeply-clone nested objects is essential for so many JavaScript tasks, particularly when we have to avoid mutating state objects in frameworks such as React/Redux. I hope you find this brief illustration of how to use the spread operator to get to all levels of your object helpful.

Happy coding!