DEV Community

Cover image for JavaScript: Using the spread operator with nested objects
Monica Gerard
Monica Gerard

Posted on

JavaScript: Using the spread operator with nested objects

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

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)

Enter fullscreen mode Exit fullscreen mode

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 } }
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
Enter fullscreen mode Exit fullscreen mode

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 } }
}
Enter fullscreen mode Exit fullscreen mode

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?

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)
Enter fullscreen mode Exit fullscreen mode

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 } }
}
Enter fullscreen mode Exit fullscreen mode

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:

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)
Enter fullscreen mode Exit fullscreen mode

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 } }  
}
Enter fullscreen mode Exit fullscreen mode

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 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?

object, One_C:, Two_B:
Enter fullscreen mode Exit fullscreen mode

Next, set up the nesting structure with curly braces:

{ object, One_C: { , Two_B: {} } }
Enter fullscreen mode Exit fullscreen mode

Finally add the spread operators, making sure you access each object:

const newObject = {
  ...object,
  One_C: { ...object.One_C, Two_B: { ...object.One_C.Two_B } }
}
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)