Best way to copy an object in JavaScript?

Object.assign()

So I'm always looking for a way to use vanilla JS whenever possible these days, and I discovered that deep copying an object in JavaScript is still weird.

StackOverflow reminded me of the JSON.parse( JSON.stringify( obj ) ) trick, and it looks like Object.assign still doesn't copy nested objects.

jQuery's $.extend() works. But that's not vanilla JS any more.

What hacks do you guys use for copying JS objects?

¯\_(ツ)_/¯

Did you find this post useful? Show some love!
DISCUSSION (19)

I use spread to copy and assign a new value to the old object

const copy=old=>{
  return {...old};
}

const add=(old,val)=>{
  return {...old,...val};
}

but this is the same as Object.assign({}, obj);, is not a deep copy.

In plain javascript there are only one truth.

JSON.parse( JSON.stringify( obj ) );

For me, in some cases, i don't know how and why, but Object.assign is working, but it's not recommended for use without deep understanding of it.

This is great for simple object literals. But, if someone out there tries it and it doesn't work, allow me to offer some reasons why.

  • JSON.parse will fail for any object that recurses on itself. Try this in your console: var a = {}; a.a = a; JSON.stringify(a);. This is uncommon in app-level code, but happens a lot in DOM parsing libraries and frameworks.
  • This method doesn't allow you to make "shallow copies" (copies of the immediate properties of the object, without going any deeper). jQuery and Lodash, on the other hand, have options that allow you to do this.
  • Objects that have a toJSON() method will not truly be copied as-is. The toJSON() method will be invoked and its response will be assumed to be correct JSON, whether or not it corresponds to the actual properties of the object.
  • Methods will not be copied.
  • Dates will be parsed as Strings, not as Dates.

To cover your own butt, you're better off doing as others have suggested and using jQuery or Lodash. In modern ES6 code, you don't have to worry about code bloat, since you can just import the method you want to use from either toolkit and the rest of the library won't get included in your browser bundle.

JSON.parse(JSON.stringify(obj)) doesn't work for things that don't get stringified:

> JSON.parse(JSON.stringify({ a: () => 1, b: 2, c: null, d: undefined }))
{ b: 2, c: null }
> 

If you don't want to use a library or module for this, I think a for..in (with a hasOwnProperty) check is probably the best bet.

Wouldn't 'object.c === undefined' be true for both the first and the second, meaning that in pretty much every sense of the word, the two are indeed equal?

Sort of, but depending on how you check for what's in an object it wouldn't be:

> const a = {}
undefined
> const b = { thing: undefined }
undefined
> 'thing' in a
false
> 'thing' in b
true
> a.hasOwnProperty('thing')
false
> b.hasOwnProperty('thing')
true
> Object.keys(a).length
0
> Object.keys(b).length
1

etc.

Well I suppose it depends on how deep the objects are and whether you have control over the organization of the objects. I believe for simple scenarios, you could do something along the following. Dunno if you qualify ES6 as Vanilla JS, so here's some ES5:

function Garage(x, y, cars) {
  // dimensions of the garage in feet, because America :/
  this.area = {
    width: x,
    depth: y,
  }
  this.cars = cars;

  this.copy = function(garage){
    this.area.width = garage.area.width;
    this.area.depth = garage.area.depth;
    for (var i = 0; i < garage.cars.length; i++) {
      this.cars[i] = new Car(garage.cars[i].make, garage.cars[i].model);
    }
  };
}

function Car(make, model) {
  this.make = make;
  this.model = model;
}

(function(){
  var someGarage = new Garage(40.0, 25.0, [
    new Car("Toyota", "Camry"),
    new Car("Toyota", "Prius"),
    new Car("Tesla", "Model 3"),
  ]);
  var otherGarage = new Garage(25.0, 25.0, [
    new Car("Toyota", "Camry"),
  ]);

  // make the second garage a copy of the first, without using references
  otherGarage.copy(someGarage);

  // change the first garage to show that the garages aren't related
  someGarage.cars[2] = null;
  someGarage.area.width++;

  console.log("someGarage: ", someGarage);
  console.log("otherGarage: ", otherGarage);
})();

The output to the console:

someGarage:  {}
    area: Object { width: 41, depth: 25 }
    cars: []
        0: Object { make: "Toyota", model: "Camry" }
        1: Object { make: "Toyota", model: "Prius" }
        2: null
        length: 3
        __proto__: Array []
    copy: function Garage/this.copy()
    __proto__: Object {  }

otherGarage:  {}
    area: Object { width: 40, depth: 25 }
    cars: []
        0: Object { make: "Toyota", model: "Camry" }
        1: Object { make: "Toyota", model: "Prius" }
        2: Object { make: "Tesla", model: "Model 3" }
        length: 3
        __proto__: Array []
    copy: function Garage/this.copy()
    __proto__: Object {  }

Alternatively, if you have to handle dynamic objects of unknown dimensions, you could use a recursive function that uses Object.getOwnPropertyNames and the prototypes (someObject.__proto__) of those properties to handle the generation of fresh Objects for the properties of the destination object.

Hope that answers your question!

Edit: Added console output for the above script.

That does indeed look like a good solution if you know the object props.

But JSON.parse( JSON.stringify( obj ) );, though hacky, is much quicker to write!

Oh okay. I misunderstood, I thought you were avoiding using that JSON.parse( JSON.stringify( obj ) ); for some reason. Whoops!

Have a good Turkey day, or a good Thursday.

I would simply use recursion for a deep copy:

function deepCopy(obj) {
 if(typeof obj === 'object') {
  return Object.keys(obj)
   .map(k => ({ [k]: deepCopy(obj[k]) }))
   .reduce((a, c) => Object.assign(a, c), {});
 } else if(Array.isArray(obj)) {
  return obj.map(deepCopy)
 }
 return obj;
}

Good to see I'm not crazy! JSON.parse( JSON.stringify( obj ) ); seems to be a common 'shortcut'. But assigning each prop to a new object with obj.hasOwnProperty(item) also looks like a good option if you know which properties to look for.

Good ol' var copy = {}; for (var item in obj) { obj.hasOwnProperty(item) && (copy[item] = obj[item]); } approach works most of the times.

About those cases when this doesn't work: maybe you're solving the wrong problem.

I'm all in for vanilla JS but to a point, I would suggest copy the function from Lodash or jQuery. Use the code that was written and tested over the years, circular references can be a pain in the ..code.

So you're saying Object.assign({}, obj); doesn't work?

It does work, but not for nested objects. i.e.:

const x = {
  foo: {
    bar: false
  }
}
const y = Object.assign({}, x);
console.log(x === y); // outputs false
console.log(x.foo === y.foo); // outputs true

Yeah it doesn't work on nested objects.

Normally I would use lodash's clone() or cloneDeep() to do that, if I have it into the project already, otherwise the good old JSON.parse( JSON.stringify( obj ) ); is always a great option.

I would use lodash and its assignIn method. I haven't tried yet Object.assign() so I don't know if it copies nested object and references.

What happens to cycles? For example

let a = {};
a.a = a;

Is the assumption that only data objects with no functions or cycles are being copied?

Classic DEV Post from Apr 16

How to begin a project with just an idea

Hi, I have an idea of an application, I'm excited about it, I'm a junior, I'm g...

Peter Tasker
Programmer in Ottawa Canada.

A blogging community of over 100,000 software developers Join dev.to