Deep copying in JS !

- 3 mins

The Problem

Now-a-days, I’m working on the ringleader project. And just yesterday, me and one of my team mates Utkarsh observed that there were a couple of errors being generated in our javascript code. 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.

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'

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 { }

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.

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!

rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium vimeo