Learn to Code via Tutorials on Repl.it!

← Back to all posts
Deep Cloning Objects
h
Coder100 (18922)

Deep Cloning Objects

Why should one even be interested in this one may ask? Because it will most definitely affect you in many aspects. This is one of the most annoying properties of JS.

const myArr = [1, 2, 3];
let copy = myArr;

copy[3] = 4;
console.log(copy, myArr); // [1, 2, 3, 4], [1, 2, 3, 4]

?!?! myArr is constant!! wtf!!

Reason

I am sure you will find a real world application that concerns two arrays like in this example. Before we discuss a solution, let's first talk about why this is.

When you pass in numbers to be copied:

const myNum = 3;
let copy = myNum;

copy++;
console.log(copy, myNum); // 4, 3

And even strings (remember, in JS, strings are not array pointers like in C and C++):

const myNum = "123";
let copy = myNum;

copy += "4";
console.log(copy, myNum); // 1234, 123

it copies by value. This is very much what is expected. In the backend, what happens is that the value is passed, and everyone is happy! However, objects and arrays in the backend are just pointers to memory addresses, and you are being given the pointer! Not what you want at all!

Shallow Copy

Naively, to combat this, one would probably do something like this:

const myArr = [1, 2, 3];
let copy = [...myArr];

copy[3] = 4;
console.log(copy, myArr); // [1, 2, 3, 4], [1, 2, 3]

And all is good! However, is it though?

const myArr = [{a:1}, {a:2}, {a:3}];
let copy = [...myArr];

copy[2].a = 4;
console.log(copy, myArr); // [{a:1},{a:2}, {a:4}], [{a:1},{a:2}, {a:4}]

That's right, it only made a shallow copy. Only the first things got copied, nothing else!

Deep Copy

To implement a deep copy, all we need to do is deep copy objects within objects. It's quite simple, and here's my implementation of it:

function copyObj(obj) {
  if (typeof obj != "object") return obj;

  let out = {};
  for (const key of Object.keys(obj)) out[key] = copy(obj[key]);
  return out;
}

If the argument isn't an object (which we will see later), we just return the object as it will be passed by value, and if it is an object, we just create a new object that gets assigned key by key with copies of the value of the key.

Now, our example looks like this:

const myArr = [{a:1}, {a:2}, {a:3}];
let copy = copyObj(myArr);

copy[2].a = 4;
console.log(copy, myArr); // { '0':{a:1}, '1':{a:2}, '2':{a:4}}, [{a:1}, {a:2}, {a:3}]

it copied! However, the output isn't really an array anymore...

Array Copy

For arrays, we just need to map the values, and the function looks something like this:

function copyArr(items) {
  return items.map(item => Array.isArray(item) ? copyArr(item) : item);
}

A very nice recursive solution like the object copy.

Conclusion

Everything is passed by value except for objects. Those are stored as pointers, and will be passed by reference. In order to remedy this, we defined two functions.

function copyObj(obj) {
  if (typeof obj != "object") return obj;

  let out = {};
  for (const key of Object.keys(obj)) out[key] = copy(obj[key]);
  return out;
}

function copyArr(items) {
  return items.map(item => Array.isArray(item) ? copyArr(item) : item);
}

And special thanks to @fuzzyastrocat, here's a function that accomplishes both:

function copyObj(obj) {
  if (typeof obj != "object") return obj;

  let out = {};
  for (const key of Object.keys(obj)) out[key] = copy(obj[key]);
  return out;
}

function copyArr(items) {
  return items.map(item => Array.isArray(item) ? copyArr(item) : item);
}

function copy(obj) {
  if (typeof obj != "object") return obj;
  if (Array.isArray(obj)) return copyArr(obj);
  return copyObj(obj);
}

Now, with these functions at your disposal, go forth and complete the aoc!

Comments
hotnewtop
CursorsDev (729)

owo i cant believe that JSON.parse(JSON.stringify(object)) deep copies the object lmao

CursorsDev (729)

i mean your typical object

RayhanADev (2614)

I wish I understood any of this ( -.-)

Me before reading this:

Dev.to: Pick your skill level
Me: 7/10

Me after reading this:

Dev.to: Pick your skill level
Me: -1/10
HackermonDev (2074)

YES! I always have problems with this while coding, thx for the tutorial.

xxpertHacker (931)

What about TypedArrays, Sets, Maps, DataViews, ArrayBuffers, DOM objects, RegExps, Dates, etc?

Kasey00 (0)

Note that your solution won't preserve classes either https://vidmate.bet/ (unless I've missed something).

fuzzyastrocat (1867)

Nice, however I'd put a word of caution in this. By definition, cloning an object is expensive performance-wise (and memory-wise for that matter). So, if you're needing to use this a lot then there's probably a better, more efficient way to solve the problem.

However, this is definitely needed sometimes, so I'm not saying it should never be done.

(Is that a typo on line 5 of the copyObj function? Shouldn't it be copyObj( not copy(?)

Also, here's a single function that can do both objects and arrays:

function copyObj(obj) {
  let out = {};
  for (const key of Object.keys(obj)) out[key] = copy(obj[key]);
  return out;
}

function copyArr(items) {
  return items.map(item => copy(item));
}

function copy(obj) {
  if (typeof obj != "object") return obj;
  if (Array.isArray(obj)) return copyArr(obj);
  return copyObj(obj);
}
Coder100 (18922)

definitely!
I don't encounter this often (previously I just gave up and rethought my implementation), but I found this useful for tiny projects like an AOC challenge @fuzzyastrocat

fuzzyastrocat (1867)

@Coder100 Cool, thanks for adding it to the post!
And yeah, it can definitely be super useful for little tricky problems.

iocoder (164)

Thisis cool

@Coder100
Edit: sometimes i'll see typeof === object, or something like that, in the JS console when I'm running html or nodejs, does that have anything to do with this?

Coder100 (18922)

@ridark yeah, it might, it really depends.
typeof === 'object' tells you if it is an object, but maybe they are just checking if it is an object lol

Zavexeon (1166)

You can also clone them like:

const clone = JSON.parse(JSON.stringify(object))
programmeruser (615)

@Zavexeon the problem is that native objects aren't stored correctly. For example new Date() becomes "2020-12-08T20:26:56.863Z".

Coder100 (18922)

yuck, you won't be able to preserve classes @Zavexeon

fuzzyastrocat (1867)

@Coder100 Note that your solution won't preserve classes either (unless I've missed something).

fuzzyastrocat (1867)

@Coder100 Here's an example (I assume that's what you mean by "instances"):

class Test {}
new Test() instanceof Test // => true
copyObj(new Test()) instanceof Test // => false
JBloves27 (1903)

Pretty cool! after i learn JS, ima have to read this ;)

Coder100 (18922)

thx :))
you will very very very much want it! It took me so long to find out about why this even was a thing xd @JBYT27