[javascript] Deep copy in ES6 using the spread syntax

I am trying to create a deep copy map method for my Redux project that will work with objects rather than arrays. I read that in Redux each state should not change anything in the previous states.

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    output[key] = callback.call(this, {...object[key]});

    return output;
    }, {});
}

It works:

    return mapCopy(state, e => {

            if (e.id === action.id) {
                 e.title = 'new item';
            }

            return e;
        })

However it does not deep copy inner items so I need to tweak it to:

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    let newObject = {...object[key]};
    newObject.style = {...newObject.style};
    newObject.data = {...newObject.data};

    output[key] = callback.call(this, newObject);

    return output;
    }, {});
}

This is less elegant as it requires to know which objects are passed. Is there a way in ES6 to use the spread syntax to deep copy an object?

This question is related to javascript ecmascript-6 redux spread-syntax

The answer is


I often use this:

function deepCopy(obj) {
    if(typeof obj !== 'object' || obj === null) {
        return obj;
    }

    if(obj instanceof Date) {
        return new Date(obj.getTime());
    }

    if(obj instanceof Array) {
        return obj.reduce((arr, item, i) => {
            arr[i] = deepCopy(item);
            return arr;
        }, []);
    }

    if(obj instanceof Object) {
        return Object.keys(obj).reduce((newObj, key) => {
            newObj[key] = deepCopy(obj[key]);
            return newObj;
        }, {})
    }
}

Here's my deep copy algorithm.

const DeepClone = (obj) => {
     if(obj===null||typeof(obj)!=='object')return null;
    let newObj = { ...obj };

    for (let prop in obj) {
      if (
        typeof obj[prop] === "object" ||
        typeof obj[prop] === "function"
      ) {
        newObj[prop] = DeepClone(obj[prop]);
      }
    }

    return newObj;
  };

Instead use this for deep copy

var newObject = JSON.parse(JSON.stringify(oldObject))

_x000D_
_x000D_
var oldObject = {_x000D_
  name: 'A',_x000D_
  address: {_x000D_
    street: 'Station Road',_x000D_
    city: 'Pune'_x000D_
  }_x000D_
}_x000D_
var newObject = JSON.parse(JSON.stringify(oldObject));_x000D_
_x000D_
newObject.address.city = 'Delhi';_x000D_
console.log('newObject');_x000D_
console.log(newObject);_x000D_
console.log('oldObject');_x000D_
console.log(oldObject);
_x000D_
_x000D_
_x000D_


function deepclone(obj) {
    let newObj = {};

    if (typeof obj === 'object') {
        for (let key in obj) {
            let property = obj[key],
                type = typeof property;
            switch (type) {
                case 'object':
                    if( Object.prototype.toString.call( property ) === '[object Array]' ) {
                        newObj[key] = [];
                        for (let item of property) {
                            newObj[key].push(this.deepclone(item))
                        }
                    } else {
                        newObj[key] = deepclone(property);
                    }
                    break;
                default:
                    newObj[key] = property;
                    break;

            }
        }
        return newObj
    } else {
        return obj;
    }
}

I myself landed on these answers last day, trying to find a way to deep copy complex structures, which may include recursive links. As I wasn't satisfied with anything being suggested before, I implemented this wheel myself. And it works quite well. Hope it helps someone.

Example usage:

OriginalStruct.deep_copy = deep_copy; // attach the function as a method

TheClone = OriginalStruct.deep_copy();

Please look at https://github.com/latitov/JS_DeepCopy for live examples how to use it, and also deep_print() is there.

If you need it quick, right here's the source of deep_copy() function:

function deep_copy() {
    'use strict';   // required for undef test of 'this' below

    // Copyright (c) 2019, Leonid Titov, Mentions Highly Appreciated.

    var id_cnt = 1;
    var all_old_objects = {};
    var all_new_objects = {};
    var root_obj = this;

    if (root_obj === undefined) {
        console.log(`deep_copy() error: wrong call context`);
        return;
    }

    var new_obj = copy_obj(root_obj);

    for (var id in all_old_objects) {
        delete all_old_objects[id].__temp_id;
    }

    return new_obj;
    //

    function copy_obj(o) {
        var new_obj = {};
        if (o.__temp_id === undefined) {
            o.__temp_id = id_cnt;
            all_old_objects[id_cnt] = o;
            all_new_objects[id_cnt] = new_obj;
            id_cnt ++;

            for (var prop in o) {
                if (o[prop] instanceof Array) {
                    new_obj[prop] = copy_array(o[prop]);
                }
                else if (o[prop] instanceof Object) {
                    new_obj[prop] = copy_obj(o[prop]);
                }
                else if (prop === '__temp_id') {
                    continue;
                }
                else {
                    new_obj[prop] = o[prop];
                }
            }
        }
        else {
            new_obj = all_new_objects[o.__temp_id];
        }
        return new_obj;
    }
    function copy_array(a) {
        var new_array = [];
        if (a.__temp_id === undefined) {
            a.__temp_id = id_cnt;
            all_old_objects[id_cnt] = a;
            all_new_objects[id_cnt] = new_array;
            id_cnt ++;

            a.forEach((v,i) => {
                if (v instanceof Array) {
                    new_array[i] = copy_array(v);
                }
                else if (v instanceof Object) {
                    new_array[i] = copy_object(v);
                }
                else {
                    new_array[i] = v;
                }
            });
        }
        else {
            new_array = all_new_objects[a.__temp_id];
        }
        return new_array;
    }
}

Cheers@!


const a = {
  foods: {
    dinner: 'Pasta'
  }
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

Using JSON.stringify and JSON.parse is the best way. Because by using the spread operator we will not get the efficient answer when the json object contains another object inside it. we need to manually specify that.


Here is the deepClone function which handles all primitive, array, object, function data types

_x000D_
_x000D_
function deepClone(obj){_x000D_
 if(Array.isArray(obj)){_x000D_
  var arr = [];_x000D_
  for (var i = 0; i < obj.length; i++) {_x000D_
   arr[i] = deepClone(obj[i]);_x000D_
  }_x000D_
  return arr;_x000D_
 }_x000D_
_x000D_
 if(typeof(obj) == "object"){_x000D_
  var cloned = {};_x000D_
  for(let key in obj){_x000D_
   cloned[key] = deepClone(obj[key])_x000D_
  }_x000D_
  return cloned; _x000D_
 }_x000D_
 return obj;_x000D_
}_x000D_
_x000D_
console.log( deepClone(1) )_x000D_
_x000D_
console.log( deepClone('abc') )_x000D_
_x000D_
console.log( deepClone([1,2]) )_x000D_
_x000D_
console.log( deepClone({a: 'abc', b: 'def'}) )_x000D_
_x000D_
console.log( deepClone({_x000D_
  a: 'a',_x000D_
  num: 123,_x000D_
  func: function(){'hello'},_x000D_
  arr: [[1,2,3,[4,5]], 'def'],_x000D_
  obj: {_x000D_
    one: {_x000D_
      two: {_x000D_
        three: 3_x000D_
      }_x000D_
    }_x000D_
  }_x000D_
}) ) 
_x000D_
_x000D_
_x000D_


const cloneData = (dataArray) => {
    newData= []
    dataArray.forEach((value) => {
        newData.push({...value})
    })
    return newData
}
  • a = [{name:"siva"}, {name:"siva1"}] ;
  • b = myCopy(a)
  • b === a // false`

// use: clone( <thing to copy> ) returns <new copy>
// untested use at own risk
function clone(o, m){
  // return non object values
  if('object' !==typeof o) return o
  // m: a map of old refs to new object refs to stop recursion
  if('object' !==typeof m || null ===m) m =new WeakMap()
  var n =m.get(o)
  if('undefined' !==typeof n) return n
  // shallow/leaf clone object
  var c =Object.getPrototypeOf(o).constructor
  // TODO: specialize copies for expected built in types i.e. Date etc
  switch(c) {
    // shouldn't be copied, keep reference
    case Boolean:
    case Error:
    case Function:
    case Number:
    case Promise:
    case String:
    case Symbol:
    case WeakMap:
    case WeakSet:
      n =o
      break;
    // array like/collection objects
    case Array:
      m.set(o, n =o.slice(0))
      // recursive copy for child objects
      n.forEach(function(v,i){
        if('object' ===typeof v) n[i] =clone(v, m)
      });
      break;
    case ArrayBuffer:
      m.set(o, n =o.slice(0))
      break;
    case DataView:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.byteLength))
      break;
    case Map:
    case Set:
      m.set(o, n =new (c)(clone(Array.from(o.entries()), m)))
      break;
    case Int8Array:
    case Uint8Array:
    case Uint8ClampedArray:
    case Int16Array:
    case Uint16Array:
    case Int32Array:
    case Uint32Array:
    case Float32Array:
    case Float64Array:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.length))
      break;
    // use built in copy constructor
    case Date:
    case RegExp:
      m.set(o, n =new (c)(o))
      break;
    // fallback generic object copy
    default:
      m.set(o, n =Object.assign(new (c)(), o))
      // recursive copy for child objects
      for(c in n) if('object' ===typeof n[c]) n[c] =clone(n[c], m)
  }
  return n
}

From MDN

Note: Spread syntax effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays as the following example shows (it's the same with Object.assign() and spread syntax).

Personally, I suggest using Lodash's cloneDeep function for multi-level object/array cloning.

Here is a working example:

_x000D_
_x000D_
const arr1 = [{ 'a': 1 }];_x000D_
_x000D_
const arr2 = [...arr1];_x000D_
_x000D_
const arr3 = _.clone(arr1);_x000D_
_x000D_
const arr4 = arr1.slice();_x000D_
_x000D_
const arr5 = _.cloneDeep(arr1);_x000D_
_x000D_
const arr6 = [...{...arr1}]; // a bit ugly syntax but it is working!_x000D_
_x000D_
_x000D_
// first level_x000D_
console.log(arr1 === arr2); // false_x000D_
console.log(arr1 === arr3); // false_x000D_
console.log(arr1 === arr4); // false_x000D_
console.log(arr1 === arr5); // false_x000D_
console.log(arr1 === arr6); // false_x000D_
_x000D_
// second level_x000D_
console.log(arr1[0] === arr2[0]); // true_x000D_
console.log(arr1[0] === arr3[0]); // true_x000D_
console.log(arr1[0] === arr4[0]); // true_x000D_
console.log(arr1[0] === arr5[0]); // false_x000D_
console.log(arr1[0] === arr6[0]); // false
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
_x000D_
_x000D_
_x000D_


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to ecmascript-6

"Uncaught SyntaxError: Cannot use import statement outside a module" when importing ECMAScript 6 where is create-react-app webpack config and files? Can (a== 1 && a ==2 && a==3) ever evaluate to true? How do I fix "Expected to return a value at the end of arrow function" warning? Enums in Javascript with ES6 Home does not contain an export named Home How to scroll to an element? How to update nested state properties in React eslint: error Parsing error: The keyword 'const' is reserved Node.js ES6 classes with require

Examples related to redux

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop Receiving "Attempted import error:" in react app Local package.json exists, but node_modules missing What is the best way to redirect a page using React Router? How do I fix "Expected to return a value at the end of arrow function" warning? ReactJS lifecycle method inside a function Component How to overcome the CORS issue in ReactJS Attach Authorization header for all axios requests React - Display loading screen while DOM is rendering?

Examples related to spread-syntax

Deep copy in ES6 using the spread syntax How to deep merge instead of shallow merge?