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 algorithm.
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!!
Great article! I think this knowledge is a must when entering any redux related environment.
Instead of ‘immutability-helper’ you can also use CloneDeep method of lodash library.
https://lodash.com/docs/4.17.15#cloneDeep
you can simply use:
const copy = JSON.parse(JSON.stringify(pizzas))
Thanks
But do note that JSON serialization can cause loss of information.