Codementor Events

Deep copying an object in JavaScript

Published Aug 16, 2018Last updated Mar 13, 2023
Deep copying an object in JavaScript

Using the spread syntax or Object.assign() is a standard way of copying an object in JavaScript. Both methdologies can be equivalently used to copy the enumerable properties of an object to another object, with the spread syntax being the shorter of the two. They are also useful to merge objects, since both methods automatically overwrite the properties in the target object that have the same keys of those in the source object.

These two techniques have been introduced in ECMAScript 2015 and are both JavaScript standard features. They are also suggested in the Redux documentation, since reducers in Redux return a copy of the state instead of mutating it directly.
However, those two methods cannot be used to make deep copies of objects.

The problem

The spread syntax and the Object.assign() method can only make shallow copies of objects. This means that the deeply nested values inside the copied object are put there just as a reference to the source object. If we modify a deeply nested value of the copied object, we will therefore end up modifying the value in the source object.

Let's take as an example the object below:

const pizzas = {
   margherita: {
      toppings: ['tomato sauce', 'mozzarella cheese'],
      prices: {
         small: '5.00',
         medium: '6.00',
         large: '7.00'
      }
   },
   prosciutto: {
      toppings: ['tomato sauce', 'mozzarella cheese', 'ham'],
      prices: { 
         small: '6.50', 
         medium: '7.50',
         large: '8.50' 
      }
   }
};

Let's try now to copy that pizzas object above using the spread syntax and change the value of one of the prices in the copied object:

let pizzasCopy = {...pizzas};

// modify a value in the copy of pizzas
pizzasCopy.margherita.prices.small = '5.50';

// log the copied object to the console
console.log(pizzasCopy.margherita.prices.small); // This will log 5.50, as expected

// log the source object to the console
console.log(pizzas.margherita.prices.small); // This will also log 5.50 instead of 5.00!!

As you can see, prices are deeply nested properties (more than one level deep) in our object. We only reassigned the value of one of the prices in the copied pizzasCopy object but we actually changed the same price value in the source pizzas object.
This would not happen if we reassigned the value of a top-level property:

// reassign the value of a top-level property in the copied object
pizzasCopy.margherita = {};

// log the copied object to the console
console.log(pizzasCopy.margherita); // This will log an empty object, as expected

// log the source object to the console
console.log(pizzas.margherita); // This will still log the original source object

The same will happen if we use Object.assign():

let pizzasCopy = Object.assign({}, pizzas);

// modify a value in the copy of pizzas
pizzasCopy.margherita.prices.small = '5.50';

// log the copied object to the console
console.log(pizzasCopy.margherita.prices.small); // This will log 5.50, as expected
 
// log the source object to the console
console.log(pizzas.margherita.prices.small); // This will also log 5.50 instead of 5.00!!

A solution

immutability-helper is an easy-to-use, lightweight JavaScript library commonly used in React, which allows us to mutate a copy of an object without changing the original source.
We can get this library via NPM: npm install immutability-helper --save.
To deep copy our pizza object, we could use the update() method available in immutability-helper, passing the object we want to copy as the first argument and the actual data to change as the second one.

import update from 'immutability-helper';

const pizzasCopy = update(pizzas, {margherita: {prices: {small: {$set: '5.50'}}}});

// log the copied object to the console
console.log(pizzasCopy.margherita.prices.small); // This will log 5.50, as expected

// log the source object to the console
console.log(pizzas.margherita.prices.small); // This will correctly log 5.00, the original price!!

This library as a whole bunch of useful commands and it can copy methods as well. Method definitions on objects cannot be copied for example using the 'standard' deep copying technique of JSON stringifying and parsing an object like this const copiedObj = JSON.parse(JSON.stringify(sourceObj));.

Wrapping up

  • The spread syntax and Object.assign() allow us to make only shallow copies of objects in JavaScript. Deeply nested values are in fact put there just as a reference to the source object.
  • immutability-helper is an easy-to-use, lightweight library that allows us to deep copy an object and easily manipulate it with dedicated methods.

2023 update 🗓️

structuredClone is an HTML DOM API method that creates a deep clone of a given value. It returns a deep copy of the original value, using the structured clone algortihm.

const pizzasCopy = structuredClone(pizzas)

// log the copied object to the console
console.log(pizzasCopy.margherita.prices.small); // This will log 5.50, as expected

// log the source object to the console
console.log(pizzas.margherita.prices.small); // This will correctly log 5.00, the original price!!
Discover and read more posts from Ramón Miklus
get started
post commentsBe the first to share your opinion
Jerzy Wozniak
4 years ago

Great article! I think this knowledge is a must when entering any redux related environment.

Apurva Bhasale
5 years ago

Instead of ‘immutability-helper’ you can also use CloneDeep method of lodash library.

https://lodash.com/docs/4.17.15#cloneDeep

Kushal Atreya
5 years ago

you can simply use:
const copy = JSON.parse(JSON.stringify(pizzas))

Angerla Nguyen
5 years ago

Thanks

dannyz
5 years ago

But do note that JSON serialization can cause loss of information.

JSON.parse(JSON.stringify({ results: Infinity }));
// will produce { results: null }
Show more replies