Codementor Events

Deep Copying in JS

Published May 09, 2017Last updated Mar 05, 2018
Deep Copying in JS

The Problem

Just yesterday, me and my friend Utkarsh observed that there were a couple of errors being generated in the javascript code we were working. Some investigation revealed the source of errors to be a copy of an array which contained the same reference as the original array. Thus, a change in the copy was leading to a change in the original array as well. So, we needed to create a deep copy of the original array to overcome this problem.

By default, when copying by =, javascript keeps the reference to the same object (called a shallow copy).

var a = [1,2,3];
var b = a;
a[0] = 5;
console.log(b[0]); // 5

What All we Tried

Our initial thought was to use Object.create.

  • Using Object.create:

Object.create seemed to solve the problem when we first looked at it.

var obj = {name: 'a'};
var obj_copy = Object.create(obj);
obj_copy.name = 'c';
console.log(obj.name); // 'a'

But it was later that we realized that since we had object(s) inside an array, Object.create didn't work correctly:

var arr = [{name: 'a'}];
var arr_copy = Object.create(arr);
arr_copy[0].name = 'c';
console.log(arr[0].name); // 'c'

Turns out, even the copies made out of Object.create are not independantly referenced in this case:

var arr = [{name: 'a'}];
var arr_copy_1 = Object.create(arr);
var arr_copy_2 = Object.create(arr);

// Now, arr_copy_1 and arr_copy_2 still point to the same reference.

arr_copy_1[0].name = 'c';
console.log(arr_copy_2[0].name); // 'c'
  • Using Object.create with arrays:

On applying Object.create to an array, the result wasn't actually a real array (it was a pseduo array)!

var a = [1,2,3];
var b = Object.create(a);
console.log(b); // Object { }
  • Using Object.create with Array.from:

To convert the previously obtained pseduo array into a real array, we tried using Array.from:

var a = [1,2,3];
var b = Array.from(Object.create(a));
var c = Array.from(Object.create(a));
b[0] = 8;
console.log(c[0]); // 1

At this point we believed that we have the correct solution, until we found out the array which we were trying to deep copy wasn't simply composed of numbers or strings, it was deeply nested with objects, arrays and functions.

var a = [{name: 1},{name: 2},{name: 3}];
var b = Array.from(Object.create(a));
var c = Array.from(Object.create(a));
b[0].name = 8;
console.log(c[0].name); // 8

We gave a few more tries to convert the pseduo array into a real array, but each time the reference of it's consisting arrays or objects was still maintained in the copies.

  • Using JSON.stringify and JSON.parse:

Continuing further, we tried using another method altogether. But this again did not work since our array contained objects which had functions inside them. On JSON.stringify ing an object, all it's non-serializable properties are lost:

var a = {name: 'a', exec: function() {return true;}};
var b = JSON.parse(JSON.stringify(a));
console.log(b); // {name: 'a'}

The Solution

Finally, on a bit of Googling we found this wonderful utility function which does exactly what we need. It's a recursive function that copies an element's value while handling the cases for the value being an object or an array:

function copy(o) {
   var output, v, key;
   output = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       output[key] = (typeof v === "object") ? copy(v) : v;
   }
   return output;
}

So now, the following works:

var a = [{name: 1},{name: 2},{name: 3}];

var b = copy(a);
var c = copy(a);
b[0].name = 8;
console.log(c[0].name); // 1

The problem resulted to be tougher than we expected, but it certainly turned out to be a great learning experience for the both of us!


UPDATE:

Object.assign to {} seems to do the trick.

var a = [{name: 1},{name: 2},{name: 3}];

var b = Object.assign({}, a);
var c = Object.assign({}, a);
b[0].name = 8;
console.log(c[0].name); // 1

Originally posted on my blog

Discover and read more posts from Avijit Gupta
get started
post comments11Replies
fires3as0n
6 years ago

Why writing about Object.create() in a post related to copying something? This method purpose is to set a prototype of an object, not to copy any of its properties at all.

Вася
6 years ago

Object.assign([], a) doesn’t do the trick, it’s still a shallow copy.

GAURAV SRIVASTAVA
6 years ago

Simply use cloneDeep from lodash library.

eg:

var objects = [{ ‘a’: 1 }, { ‘b’: 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

https://lodash.com/docs/4.17.10#cloneDeep

Show more replies