The right way to clone an object in JavaScript

OpenJavaScript 0

Last updated: January 12, 2022.

Ever tried to make a copy of a JavaScript object? The result may not have been what you expected!

In this article, we cover what the issue is and three ways to make both a ‘shallow’ and ‘deep’ copy of a JavaScript object.

The object copy issue

When we try to copy an object in arguably the most intuitive way – by saving the contents of the object to a new variable – we come across some strange behaviour in the ‘old’ object if we try to edit the ‘new’ one.

This is because a new object isn’t created at all! Copying an object in this way only creates a reference to the original object.

As such, if we edit the reference to the original object, it actually changes the original object as well!

const myObject = {
    name: "Jimmy",
    middleName: null,
    lastName: "Williams",
    hobbies: {
        hobby1: "JavaScript",
        hobby2: "JavaScript",
        hobby3: "JavaScript",
    },
    printName: function() {
        console.log(this.name + " " + this.lastName);
    }
}

const myCopy = myObject

myCopy.middleName = "Paul";
myCopy.hobbies.hobby1 = "Sport";

console.log(myCopy); 
// "Paul" is the value for middleName and "Sport" is hobby1
console.log(myObject); 
// "Paul" is also now the value for middleName and "Sport" for hobby!

Let’s look at the options for avoiding this behaviour and making a better copy!

Alternatives:

Option 1: Spreading into new object (shallow copy)

A nice way that almost works for the example above it to spread the contents of the original object into a new object using the spread operator (...). So our copy syntax would look like this:

const myCopy = {...myObject}

The results:

myCopy.middleName = "Paul";
myCopy.hobbies.hobby1 = "Sport";

console.log(myCopy); // The copy has been edited correctly
console.log(myObject); // middleName is not changed but hobby1 is "Sport"!

This method almost works – but not for nested properties!

Option 2: To JSON and back again (shallow copy)

Another alternative is to convert the original object to JSON, then convert it back and save the result in a new variable like so:

const myCopy = JSON.parse(JSON.stringify(myObject))

myCopy.middleName = "Paul";
myCopy.hobbies.hobby1 = "Sport";

console.log(myCopy); 
// "Paul" now middleName and "Sport" now hobby 1 but printName is missing!
console.log(myObject); 
// No changes

This method also almost works. The problem, however, is that anything in the original object that is not JSONable (e.g. methods) will not be present in the copy.

This method is therefore suitable for pure ‘data’ object but not those containing functionality.

Option 3: Using the Lodash .cloneDeep method (deep copy)

Unfortunately, the in-built methods discussed above have problematic features.

To make a proper, deep copy, we can make use of the Lodash external library. To do so, navigate to your preferred directory in the terminal and write:

$ npm i -g npm
$ npm i --save lodash

Then, in the main directory, import cloneDeep from the Lodash library at the beginning of your script:

import { cloneDeep } from 'lodash';

We can now use the cloneDeep method in our code:

const myObject = {
    name: "Jimmy",
    middleName: null,
    lastName: "Williams",
    hobbies: {
        hobby1: "JavaScript",
        hobby2: "JavaScript",
        hobby3: "JavaScript",
    },
    printName: function() {
        console.log(this.name + " " + this.lastName);
    }
}

const myCopy = cloneDeep(myObject);

myCopy.middleName = "Paul";
myCopy.hobbies.hobby1 = "Sport";

console.log(myCopy); // Object properly copied with changes
console.log(myObject); // No changes!

The result makes a copy of the object, including its methods. Also, the changes we make to the new object are not reflected in the original object, even for the nested properties.

Summary

To make a shallow copy of an object, we can use the spread operator (...). However, nested properties are still a reference to the original object. Another alternative is to convert the object to JSON format and then convert it back. This works for data properties but does not preserve methods in the copy.

In our view, until the native structuredClone() method becomes supported in browsers, the best way to not get caught out when copying an object is to turn to the deepClone method in the Lodash third-party library.

Related links: