Avoid pitfalls when copying objects - shallow/deep
Posted
Updated
Europe’s developer-focused job platform
Let companies apply to you
Developer-focused, salary and tech stack upfront.
Just one profile, no job applications!
A very common task is to copy an object. There are plenty of ways to copy objects in Javascript, they vary in execution speed and limitations regarding data types.
The pitfall here is copying by reference instead of by value, or better creating a shallow instead of a deep copy. Why? Well, let me explain a bit. 🤓
💰 The Pragmatic Programmer: journey to mastery. 💰 One of the best books in software development, sold over 200,000 times.
If you make a shallow copy of object A to object B, and you change any value in the object B it will also manipulate the object A, since it points to the same reference. If the copied data is a primitive it will copy the value.
A shallow copy successfully copies primitive data types, but any object will not be recursively copied, instead the reference will copied.
A deep copy creates a copy of primitive (Boolean, String, Number, BigInt, Symbol) and structural data types (Abjects, Array, Map, Set, Weakmap,...). MDN docs provide a great reference for data types.
When copying/cloning objects the data type of the properties is key to decide what method can be used.
If your object only contains primitive values (numbers, booleans, strings, etc.), you can use the following methods.
The _.clone(value)
method from lodash creates a shallow clone of value.
The performance is good and if you use the third-party library lodash already in your application it is a viable option.
// import lodash
const _ = require('lodash');
const beer = {
id: 12345,
name: 'beer',
icon: '🍺',
amount: 10,
};
const clonedBeer = _.clone(beer);
The Object.assign() method copies all enumerable own properties from one or more source objects to a target object.
It returns the target object. Object.assign
performs a shallow copy of an object.
Syntax: Object.assign(target, ...sources)
, see MDN
const beer = {
id: 12345,
name: 'beer',
icon: '🍺',
amount: 10,
};
const clonedBeer = Object.assign({}, beer);
The spread operator is one of the fastest methods and was introduced with ES6. It's clean and simple and a native method (sorry lodash).
const beer = {
id: 12345,
name: 'beer',
icon: '🍺',
amount: 10,
};
const clonedBeer = { ...beer };
All methods above create shallow copies of objects. If you have non primitive data types as properties, and you try to make a copy, the object will be copied, BUT the underlying objects will be passed by reference to the new object. This means a shallow copy instead of a deep copy has been created.
There are several methods to create a deep clone.
If you still use jQuery in your project, you can use the extend method from jQuery.
const obj = {
name: 'mario',
food: 'pizza',
};
const objCopy = jQuery.extend(true, [], obj);
JavaScript provides native methods for serialization and deserialization, basically to convert most data types to a JSON string, and then a valid JSON string to an object.
IMPORTANT: This method can't be used for copying EVERY object. If the object property (JavaScript) does not have a JSON equivalent, it will be lost in the process of serializing - deserializing.
// This will work
const beerA = {
id: 12345,
name: 'beer',
icon: '🍺',
amount: 10,
};
const clonedBeerA = JSON.parse(JSON.stringify(beerA));
// This won't work. The function drinkBeer will not be copied and new Date() will be copied as a string.
const beerB = {
id: 12345,
name: 'beer',
icon: '🍺',
amount: 10,
date: new Date(),
drinkBeer: function() {
this.amount -= 1;
},
};
const clonedBeerB = JSON.parse(JSON.stringify(beerB));
The method _.cloneDeep(value) from Lodash does exactly the same thing as _.clone(), except that it recursively clones everything in the object.
// import lodash cloneDeep
const cloneDeep = require('lodash.cloneDeep');
const beer = {
id: 12345,
name: 'beer',
icon: '🍺',
amount: 10,
date: new Date(),
drinkBeer: function() {
this.amount -= 1;
},
};
const clonedBeer = cloneDeep(beer);
There are several methods to create a deep clone of an object. I recommend to use Lodash, it's convenient and fast.
Import a single function from Lodash to reduce the size of your dependencies.
const clone = require('lodash.clone');
const clonedeep = require('lodash.clonedeep');
Thanks for reading and if you have any questions, use the comment function or send me a message @mariokandut.
References (and Big thanks): MDN. Resources for developers, by developers, Flavio
If you want to know more about Javascript, have a look at these Javascript Tutorials.
Never miss an article.