Copy Objects in JavaScript

If you’ve tried to make a copy of an object in JavaScript by creating a new variable and setting it to the object you want to copy, you’ll soon realize that it didn’t do what you expected it to do. When you update your new “copy” using your new variable, you have also unwittingly updated the original object! In JavaScript, like in many of the other object-oriented programming languages (e.g. C#, Java), when you create a new variable and set it to another object, the language is simply allowing you to create another pointer back to the original object. Any changes (mutations) you make through your new variable is causing mutations on the original object.

Here’s a snippet demonstrating the problem:

let oldEmployee = {
  firstName: 'Jack',
  lastName: 'Jones'
};

// This is bad and most likely NOT what you were going for...
let newEmployee = oldEmployee;
newEmployee.firstName = 'Jeff';
newEmployee.lastName = 'Smith';

console.log(newEmployee.firstName) // Jeff
console.log(oldEmployee.firstName) // Jeff !?!

So, what now? How can you successfully clone an object? Below, I present a few viable solutions:

JSON.stringify() and JSON.parse()

One way to solve this problem is to use the stringify() and parse() methods of the JSON object in JavaScript. It will certainly work for the example problem snippet that I shared above. And this solution is shown below:

let oldEmployee = {
  firstName: 'Jack',
  lastName: 'Jones'
};

// Let's use JSON stringify() and parse()
let newEmployee = JSON.parse(JSON.stringify(oldEmployee));
newEmployee.firstName = 'Jeff';
newEmployee.lastName = 'Smith';

console.log(newEmployee.firstName) // Jeff
console.log(oldEmployee.firstName) // Jack! Yay, the old object remained intact!

But, this solution is not ideal in all situations. If the object that you want to clone has Date objects, Regular Expressions or method definitions or in short, anything that cannot be “stringified” with the JSON object – they won’t carry over to your new object clone.

Object.assign() Method

Another solution for this problem is to use the global Object‘s assign() method. Here’s an example:

let oldEmployee = {
  firstName: 'Jack',
  lastName: 'Jones',
  generateTpsReport() {
    console.log('printing cover page...');
  }
};

// Use Object.assign() for cloning
let newEmployee = Object.assign({}, oldEmployee);
newEmployee.firstName = 'Jeff';
newEmployee.lastName = 'Smith';

console.log(newEmployee.firstName) // Jeff
console.log(oldEmployee.firstName) // Jack
oldEmployee.generateTpsReport(); // good
newEmployee.generateTpsReport(); // still good

However, this solution is not the be-all and end-all solution, either. For instance, if your original object has a reference to another object, only that reference value is copied, which will possibly cause another unintended mutation. You may have heard the terms shallow-copy and deep-copy in the context of this topic. Object.assign() does what is known as a shallow copy. Refer to the documentation for further information.

Spread Operator

If you are working in an environment where you can target modern JavaScript – EcmaScript6 (ES6) or newer versions, you can use the spread operator (the three dots or ellipses operator) to do object cloning. Example of that is shown below:

let oldEmployee = {
  firstName: 'Jack',
  lastName: 'Jones',
  manager: {
    firstName: 'Jill',
    lastName: 'Taylor'
  },
  generateTpsReport() {
    console.log('printing cover page...');
  }
};

// Use spread operator for cloning
let newEmployee = {...oldEmployee};
newEmployee.firstName = 'Jeff';
newEmployee.lastName = 'Smith';

console.log(newEmployee.firstName) // Jeff
console.log(oldEmployee.firstName) // Jack
oldEmployee.generateTpsReport(); // good
newEmployee.generateTpsReport(); // still good

newEmployee.manager.firstName = 'Tom';
console.log(oldEmployee.manager.firstName); // still bad

But note that the spread operator is functionally equivalent to Object.assign(), i.e., you’ll still face issues if your original has nested objects as they’ll be cloned as references and thus any changes to your cloned objects’ nested properties will mutate those same nested objects in the original.

Lodash to the Rescue

If you need a solution that works in all cases, your best bet is to probably set aside all pride and include the lodash library. If you’re unfamiliar, it is the utility library for JavaScript with a ton of functionality. And yes, it has a solution for cloning objects as well, in its cloneDeep method. Example below:

let oldEmployee = {
  firstName: 'Jack',
  lastName: 'Jones',
  manager: {
    firstName: 'Jill',
    lastName: 'Taylor'
  },
  generateTpsReport() {
    console.log('printing cover page...');
  }
};

// Use lodash for cloning
let newEmployee = _.cloneDeep(oldEmployee);
newEmployee.firstName = 'Jeff';
newEmployee.lastName = 'Smith';

console.log(newEmployee.firstName) // Jeff
console.log(oldEmployee.firstName) // Jack
oldEmployee.generateTpsReport(); // good
newEmployee.generateTpsReport(); // still good

newEmployee.manager.firstName = 'Tom';
console.log(oldEmployee.manager.firstName); // good
console.log(newEmployee.manager.firstName); // good

Closing Remarks

So, I know what you’re thinking… why didn’t you just lead with the Lodash example? Then I don’t have to mess with all these other half-working solutions! Well, it all depends on what you want to do. You may just be dealing with simple properties in your object where one of the other approaches I mentioned is appropriate. Thus, you can avoid brining in a third-party library and then have to consider things such as the size of the library and any other organizational issues you may have to surpass in using such libraries. In any case, don’t simply assign object a to object b. That is most likely NOT what you meant to do.

Leave a Comment

Your email address will not be published. Required fields are marked *