Alexander Nnakwue Software Engineer. React, Node.js, Python, and other developer tools and libraries.

Methods for deep cloning objects in JavaScript

7 min read 2007

Methods for Deep Cloning Objects in JavaScript

Introduction

In JavaScript, objects are like a store or collection of key-value pairs. They are a kind of structural data type, which can be seen as a collection of properties. These properties can either be values of other data types, including primitive types like Boolean, Number, undefined, etc., or even other objects. Therefore, with objects, we can build even more complex data structures.

Due to the nature of objects in JS, they are usually stored in memory and can only be copied by reference. This means that a variable does not store an object in itself, but rather an identifier, which represents an address or a reference to that particular object in memory. As such, objects cannot be treated in the same way as primitives.

With primitive data types, once variables are assigned, they cannot be copied over. Therefore, changing the value of the variable never changes the underlying primitive type. This means it is impossible to change the values of these types once they are assigned to a variable — a concept known as immutability. However, they can be combined together to derive new values.

Objects, on the other hand, are mutable data types. In this article, we will explore ways of modifying or mutating objects in JavaScript. This entails performing either shallow or deep cloning or copying with respect to general object behavior.

Introducing object behavior

To reiterate, objects are reference types, and as such, when we copy an object variable, we are indirectly creating one more reference to the same object stored somewhere else in the computer’s memory. Therefore, when an object variable is copied, only a reference to the object is copied — the actual object is not!

Let’s look at an example to understand this concept better:

let user = { name: "Alexander" }

// this instead copies a reference to the previous object
let newUser = user

In the above example, we have two variables, each making reference to the same object in memory. In this case, the variable newUser has a reference to the initially declared user variable in memory. Note that this is only possible for reference types like objects and arrays; for primitive types like a string or a Boolean, this is not the case.

Note: We can make use of the Object.is() method to determine whether the two values are actually the same value. Running console.log(Object.is(user, newUser)) on the browser console should return the Boolean value true.

Object copying methods

JavaScript offers many ways of copying objects, but they do not provide a deep copy. Performing shallow copies is the default behavior in most of the cases.

We should note that ES6 provides two shorter syntaxes for shallow copying objects in the language. They include Object.assign() and the spread syntax, which copies values of all enumerable own properties.) from one object to another.

Note: A shallow copy successfully copies primitive types like numbers and strings, but any object reference will not be recursively copied, but instead the new, copied object will reference the same initial object.

Let’s look at them one after the other:

We made a custom demo for .
No really. Click here to check it out.

Copying an object with the Object.assign() method

Among the object constructor methods, Object.assign() is used to copy the values and properties from one or more source objects to a target object. It returns the target object, which has properties and values copied from the source object.

Since Object.assign() copies property values, it is unsuitable for deep cloning. Basically, we can use this method for shallow cloning an object and for merging two or more objects into one bigger object with the same properties.

  • Syntax:
    const copied = Object.assign(target, ...sources)

    Note: In using this method, if we have matching keys in both target and source objects, the matching keys in the second object would override the first one after cloning.

  • Parameters:
    • target – target object to which values and properties are copied
    • sources – source object from which values and properties are copied
  • Return value:
    • This method returns the target object.

Now, let’s look at a very simple example of using this method to merge two objects together:

let objectA = {a: 1, b: 2}

let objectB = {c: 3, d: 4}

Object.assign(objectA, objectB)

console.log(objectA);
// → { a: 1, b: 2, c: 3, d: 4 }

Here, the target object is objectA, while the source object is objectB. Using object.assign() is similar to using the lodash clone method for shallow copying objects. Let’s look at another example:

const clone = require('lodash.clone')
var objA = { 
  a: 1,
  b: {
        c: 2,
        d: {
            e: 3
      }
  }
}
var objB = clone(objA)
objA.b.c = 30
console.log(objA)
// { a: 1, b: { c: 30, d: { e: 3 } } }
console.log(objB)
// { a: 1, b: { c: 30, d: { e: 3 } } }

Being a shallow copy, values are cloned and object references are copied —not the objects themselves. So if we edit an object property in the original object, it is also modified in the copied object since the referenced inner object is the same in this case.

Copying an object with the spread syntax

The spread operator is an ES2018 feature that adds spread properties to object literals. It provides a very convenient way to perform a shallow clone, equivalent to what Object.assign() does. With objects, the spread operator is used to create copies of existing objects with new or updated values.

It copies enumerable properties from a provided object onto a new object. Let’s see an example usage, as per the syntax:

const copied = { ...original }

Now let’s look at a real-world example:

const objA = { 
    name: 'Alexander', 
    age: 26, 
}

const objB = { 
    Licensed: true, 
    location: "Ikeja" 
}

const mergedObj = {...objA, ...objB}
console.log(mergedObj) 

// { name: 'Alexander', age: 26, Licensed: true, location: 'Ikeja' }

From the above, we can see that mergedObj is a copy of objA and objB. Actually, every enumerable property on the objects will be copied to the final mergedObj object. The spread operator is just a shorthand for the Object.assign() method, but there are some subtle differences between the two, including the fact that Object.assign() triggers setters, whereas the spread operator does not.

Note: If an object references other objects when performing a shallow copy of the object, we copy the references to the external object. When performing a deep copy, those external objects are copied as well, so the new cloned object is completely independent from the old one.

Recommended method for deep cloning objects in JavaScript

Most of the time, when we decide to copy objects in our program, our intent is to actually copy by reference, which is more or less making a shallow copy of the object. However, when it comes to deeply nested objects, the behavior of Object.assign() or spread is different.

In essence, there is no one consistent way of cloning or copying objects in the language, regardless of their structure, in terms of how the objects are constructed.

A question that arises here is copying deeply nested objects up to, say, two or three levels deep in such a way that if we make changes to the new object, it doesn’t affect the original object acting as our target. So how do we correctly deep clone an object?

To perform a deep copy, our best bet is to rely on a library that’s well tested, popular, and well maintained by the community: Lodash. Lodash offers both clone and cloneDeep functions to perform shallow and deep cloning, respectively.

For example, when deep copying objects in Node.js, we can make use of the Lodash cloneDeep() method. An example is shown below:

const cloneDeep = require('lodash.clonedeep')

let objA = {
    a: 1,
    b: {
        c: 2,
        d: {
            e: 3
        }
    }
}

// copy objA save as new variable objB
let objB = cloneDeep(objA)

// change the values in the original object objA
objA.a = 20
objA.b.c = 30
objA.b.d.e = 40

console.log(JSON.stringify(objA))
// → {"a":20,"b":{"c":30,"d":{"e":40}}}

// objB which is the cloned object is still the same
console.log(JSON.stringify(objB))
// → {"a":1,"b":{"c":2,"d":{"e":3}}}

The Lodash cloneDeep() method is similar to clone, except that it recursively clones value while preserving object inheritance. The great thing about the library is that we can import each function individually — no need to import the entire library into our project. This can greatly reduce the size of our program dependencies.

In order to make use of Lodash clone methods in Node.js, we can install it by running npm i lodash.clonedeep for deep clone and npm i lodash.clone for shallow clone. We can use it like so:

const clone = require('lodash.clone')
const cloneDeep = require('lodash.clonedeep')

const shallowCopy = clone(originalObject)
const deepCopy = clonedeep(originalObject)

Note: Copying objects derived from built-in JavaScript objects will result in extra, unwanted properties.

Native deep cloning

The HTML standard includes an internal structured cloning/serialization algorithm that can create deep clones of objects. Although still limited to certain built-in types, it can preserve references within the cloned data, allowing support for cyclical and recursive structures that would otherwise cause errors with JSON.

With support in Node.js still experimental, the v8 module exposes the structured serialization API directly. For example, cloning an object is as simple as:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

More details can be found here.

Other object cloning methods

Iterating through each object property and copying it into a new empty object

This involves iterating through a source object’s properties and copying all of them one after the other to a target object. The idea is to create a new object and replicate the structure of the existing one by iterating over its properties and copying them.

Let’s see an example:

let user = {
  name: "Alexander",
  age: 26
};

let clone = {}; // the new empty object

// let's copy all user properties into it
for (let key in user) {
  if (user.hasOwnProperty(key)) {
  clone[key] = user[key];
 }
}

// now clone is a fully independent object with the same content
clone.name = "Chinedu"; // changed the data 

console.log(user.name); // still Alexander in the original object

Cloning Objects using JSON.parse/stringify

This offers a very fast way of deep cloning objects. However, it is not very reliable and standard as it comes with some data loss along the way.

Using this method, the source object must be JSON-safe. If we do not use Date, undefined, Infinity, functions, regexps, maps, sets, or other complex types within our object, a very simple way to deep clone an object is by using:

JSON.parse(JSON.stringify(object))

Let’s look at an example:

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // string
  undef: undefined,  // lost
  inf: Infinity,  // 'null'
  re: /.*/,  // lost
}

console.log(typeof a.date) // returns  object

const clone = JSON.parse(JSON.stringify(a))

console.log(typeof clone.date)  // returns string 

console.log(clone)
// 
{
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: '2020-09-28T15:47:23.734Z',
  inf: null,
  re: {}
}

Note: This method needs some sort of exception handling to keep it safe in case the source object can not be converted to JSON.

Conclusion

By default, JavaScript always passes by value, which means changing the value of the variable never changes the underlying primitive type. However, for non-primitive data types (arrays, functions, and objects), which are passed by reference, we can always mutate the data, causing a single object value to have different content at different times.

Cloning a JavaScript object is a task that is used mostly because we do not want to create the same object if it already exists. As we are now aware, objects are assigned and copied by reference. In other words, a variable stores not the object value, but a reference. Therefore, copying such a variable or passing it as a function argument copies that reference, not the object.

For simple objects that only store primitive types like numbers and strings, shallow copying methods discussed earlier will work. A shallow copy means the first level is copied, and deeper levels are referenced. However, if the object properties reference other nested objects, the actual object won’t be copied, as we would only be copying the reference.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Debug JavaScript errors easier by understanding the context

    Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

    LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

    LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

    .
    Alexander Nnakwue Software Engineer. React, Node.js, Python, and other developer tools and libraries.

    Leave a Reply