Best way to copy an object in JavaScript?
Peter Tasker

Peter Tasker @ptasker

About: Programmer in Ottawa Canada.

Location:
Ottawa
Joined:
Apr 3, 2017

Best way to copy an object in JavaScript?

Publish Date: Nov 22 '17
27 21

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?

¯\_(ツ)_/¯

Comments 21 total

  • Forest Hoffman
    Forest HoffmanNov 23, 2017

    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.

    • Peter Tasker
      Peter TaskerNov 23, 2017

      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!

      • Forest Hoffman
        Forest HoffmanNov 23, 2017

        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.

  • Meghan (she/her)
    Meghan (she/her)Nov 23, 2017

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

    • Etienne
      EtienneNov 23, 2017

      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
      
    • Peter Tasker
      Peter TaskerNov 23, 2017

      Yeah it doesn't work on nested objects.

  • Alberto Fernandez Medina
    Alberto Fernandez MedinaNov 23, 2017

    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.

  • Alex Lohr
    Alex LohrNov 23, 2017

    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.

  • Rafael Hovhannisyan
    Rafael HovhannisyanNov 23, 2017

    In plain javascript, there is only one option.

    JSON.parse( JSON.stringify( obj ) );
    
    Enter fullscreen mode Exit fullscreen mode

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

    • Isaac Lyman
      Isaac LymanNov 25, 2017

      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.

  • Paolo Marino
    Paolo MarinoNov 23, 2017

    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.

  • Peter Tasker
    Peter TaskerNov 23, 2017

    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.

  • francoRMM
    francoRMMNov 24, 2017

    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.

  • Adrian B.G.
    Adrian B.G.Nov 24, 2017

    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.

  • Jan van Brügge
    Jan van BrüggeNov 25, 2017

    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;
    }
    
    Enter fullscreen mode Exit fullscreen mode
  • Alain Van Hout
    Alain Van HoutNov 25, 2017

    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?

  • Maginot Junior
    Maginot JuniorJun 21, 2019

    Oh boy...
    I saw a lot of comments, many with arguments like "if you do this it doesn't work". Well, please note that isn't a "not working" subject but BY DESIGN. Deep copy will not keep references for example, and thus will not be carried along. Shallow copies will keep references and so can make shit happens if you're not aware of its behaviour. It's a matter of understanding exactly what you need and what you're cloning/copying.
    Oh and BTW, undefined isn't equal to null. Undefined is a global object with a primitive "undefined" value, when setting something to undefined you are setting a reference to the undefined global object and a deep copy will not carry it along just like it wouldn't with any other references.
    Keep that in mind.

  • Ashwani Sindhu
    Ashwani SindhuOct 20, 2020

    I use this utility function for deep cloning the objects in vanilla JavaScript. It handles string properties, symbol properties and circular references in the input object.

    function cloneDeep(data, map = new Map()) {
      if (data === undefined || data === null || typeof data !== 'object') {
        return data;
      }
      if (map.has(data)) {
        return map.get(data);
      }
      const result = Array.isArray(data) ? [] : {};
      map.set(data, result);
      const allKeys = [...Object.keys(data), ...Object.getOwnPropertySymbols(data)]
      for (const key of allKeys) {
          result[key] = cloneDeep(data[key], map);
      }
    
      return result;
    }
    
    
    Enter fullscreen mode Exit fullscreen mode
Add comment