How to Copy Objects in JavaScript

Introduction

A very common task in programming, regardless of language, is to copy (or clone) an object by value, as opposed to copying by reference. The difference is that when copying by value, you then have two unrelated objects with the same value or data. Copying by reference means that you have two objects that point to the same data in memory. This means that if you manipulate object A, for example, it will also manipulate object B since they both reference the same underlying data.

In this article I'll go over a few of the ways that you can copy objects by value in JavaScript. I'll show how you can do this by using both third-party libraries and by writing your own copy function.

Note: Since Node.js is just a runtime built on the V8 JavaScript engine, all of the clone methods I show in this article will also work for Node as well.

Third-Party Libraries

There are a number of popular third-party libraries that have this functionality built-in, which we'll go over in the next few sections. In my opinion these are the best solution for most simple use-cases since they've been heavily tested and continually updated. Writing this kind of code yourself isn't easy, so it's very helpful to be able to use code that has lots of eyes on it.

Lodash

The Lodash library provides a few different methods for copying, or cloning, objects, depending on your use-case.

The most generic method is the clone() method, which provides shallow copies of objects. It works by simply passing the object as the first argument, and the copy will be returned:

const _ = require('lodash');

let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
let copy = _.clone(arrays);
console.log(copy);
{ first: [ 1, 2, 3 ], second: [ 4, 5, 6 ] }

This means that the "top level" object (or array, buffer, map, etc.) is cloned, but any deeper objects will be copied by reference. The code below demonstrates that the first array in the original arrays object is the same object as the first array in the copy object:

const _ = require('lodash');

let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
let copy = _.clone(arrays);
console.log(copy.first === arrays.first);
true

If you'd prefer that all objects, both shallow and deep objects, are copied, then you'll want to use the cloneDeep() method instead:

const _ = require('lodash');

let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
let copy = _.cloneDeep(arrays);
console.log(copy);
{ first: [ 1, 2, 3 ], second: [ 4, 5, 6 ] }

This method works by recursively cloning all values at any depth level.

Running the same equality check from above, we can see that the original and copied arrays are no longer equal since they are unique copies:

const _ = require('lodash');

let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
let copy = _.cloneDeep(arrays);
console.log(copy.first === arrays.first);
false

Lodash offers a few more clone methods, including cloneWith() and cloneDeepWith(). Both of these methods accept another parameter called customizer, which is a function used to help produce the copied value.

So if you want to use some custom copying logic then you can pass a function to handle it within Lodash's method. For example, let's say you have an object that contains some Date objects, but you want those to be converted to timestamps upon being copied, you could do that like this:

const _ = require('lodash');

let tweet = {
    username: '@ScottWRobinson',
    text: 'I didn\'t actually tweet this',
    created_at: new Date('December 21, 2018'),
    updated_at: new Date('January 01, 2019'),
    deleted_at: new Date('February 28, 2019'),
};
let tweetCopy = l.cloneDeepWith(tweet, (val) => {
    if (l.isDate(val)) {
        return val.getTime();
    }
});
console.log(tweetCopy);
{ username: '@ScottWRobinson',
  text: 'I didn\'t actually tweet this',
  created_at: 1545372000000,
  updated_at: 1546322400000,
  deleted_at: 1551333600000 }

As you can see, the only data that was altered by our method were the ones were Date objects, which have now been turned in to Unix timestamps.

Underscore

The Underscore clone() method works much the same way as Lodash's clone() method. It only provides a shallow copy of the given object, with any nested objects being copied by reference.

The same example as before demonstrates this:

const _ = require('underscore');

let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
let copy = _.clone(arrays);
console.log(copy.first === arrays.first);
true

Unfortunately the Underscore library doesn't seem to have any method to handle deep copying. You could implement this logic on your own (using some of the logic shown below) and still use Underscore's clone method for the shallow copy, or you could try one of the other solutions in this article.

Custom Solutions

As I mentioned earlier, taking on this challenge by yourself is a difficult one since there are lots of cases (and tricky edge cases) to handle when cloning an object in JavaScript. Although, if done correctly you'll then be able to add some nice customization within your method that might not be possible otherwise.

Using JSON Methods

One often-cited solution is to simply use the JSON.stringify and JSON.parse methods to your advantage, like this:

let arrays = {first: [1, 2, 3], second: [4, 5, 6]};
let copy = JSON.parse(JSON.stringify(arrays));
console.log(copy);
{ first: [ 1, 2, 3 ], second: [ 4, 5, 6 ] }

This will leave you with a deeply copied object, and it works very well for plain objects that are easily converted to JSON.

We can again verify this using the same check as from above:

console.log(copy.first === arrays.first);
false

If you know that your object is easily serializable, then this could be a good solution for you.

Writing your own from Scratch

If for some reason none of the other solutions work for you then you'll have to write your own clone method.

Since I don't trust myself to correctly implement a full clone method (and risk readers copying my mistakes in to their production code), I've copied the following clone function from this gist, which copies objects recursively and seems to work on many of the common data types you'll run in to in JavaScript.

function clone(thing, opts) {
    var newObject = {};
    if (thing instanceof Array) {
        return thing.map(function (i) { return clone(i, opts); });
    } else if (thing instanceof Date) {
        return new Date(thing);
    } else if (thing instanceof RegExp) {
        return new RegExp(thing);
    } else if (thing instanceof Function) {
        return opts && opts.newFns ? new Function('return ' + thing.toString())() : thing;
    } else if (thing instanceof Object) {
        Object.keys(thing).forEach(function (key) { newObject[key] = clone(thing[key], opts); });
        return newObject;
    } else if ([ undefined, null ].indexOf(thing) > -1) {
        return thing;
    } else {
        if (thing.constructor.name === 'Symbol') {
            return Symbol(thing.toString().replace(/^Symbol\(/, '').slice(0, -1));
        }
        return thing.__proto__.constructor(thing);
    }
}

This function works by handling specific cases when needed (like arrays, regular expressions, functions, etc.), and then for all other data types (like numbers, strings, booleans, etc.) it defaults to the thing's own constructor to copy the value. If the thing is an object itself then it just recursively calls itself on the child attributes of thing.

Check out the full gist in the link above for all of the data types and edge cases that it's been tested on.

Conclusion

While simple in theory, in practice copying an object in JavaScript is anything but simple. Luckily, there are quite a few solutions out there for you to use, like cloneDeep in Lodash, or even the built-in JSON methods. And if for any reason none of those are suitable then it is possible to write your own clone method, as long as you thoroughly test it.

Good luck, and us know if you have any thoughts, ideas, or tips in the comments.