SCROLL TO EXPLORE •SCROLL TO EXPLORE •
Be careful what you <mark>spread</mark>

Be careful what you spread

A guide to copying JS arrays & objects; safely

Louis la Grange - 3 min read

JavaScript is as well known for its object notation as it is notorious for its nuances. For example, did you know that [] == ![] will equate to true and not false as you might expect!? See this list on GitHub for an explanation and more examples.

The problem

Some time ago I encountered a strange outcome when trying to copy an array in JavaScript. For most of my development career a simple spread operator [...array] would suffice all of my array-copying needs. Very handy!

Now to explain my problem I'm going to have to get a little bit technical. The array I was trying to copy looked something like the following:

[
	{
		prop: "dummy",
		anotherprop: 123,
		subArray: [
			{
				title: "one",
				type: "DIGIT",
				value: 1
			},
			...more objects...
		]
	},
	... more objects...
]

NOTE: The parent array contains a set of objects. Each of these objects contain another array subArray with their own set of objects.

To my surprise, when I copied the parent array with the spread operator, the subArray still referenced the ORIGINAL array's subArray. This is a problem if you're trying to edit the copy while keeping the original intact.

I don't recall having an issue with the spread operator until now and looking back revealed that I either didn't notice this behaviour before, or I haven't worked with such complex nested arrays/objects before. This was new behaviour to me that warranted some investigation.

The solution

The very trusty MDN documentation for the spread operator came to my rescue. It states:

Spread syntax effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays.

Aha, the answer!

Now I know the why and reading further started to reveal the solution.

The same is true with Object.assign() — no native operation in JavaScript does a deep clone.

Deep clone? I've never heard of this term before. After doing digging some more I realised that I had to go down to the deepest level of my array/object and perform the copy from there.

I accomplished this by iterating over all of the elements in the parent array with a .map(). I then performed a spread for each parentElement, and manipulated the subArray with another iterable, such as .filter()

...map(
   return {
   	...parentElement,
   	subArray: parentElement.subArray.filter(...)
   }
)

This finally produced the result I was looking for, copying the parameters one-by-one instead of referencing the object, but it wasn't pretty.

The better solution

Luckily a much better solution was revealed in the last sentence of the paragraph for the spread operator.

The Web API method structuredClone() allows deep copying values of certain supported types.

Even though JavaScript has no native deep cloning function, there are plenty of libraries (think es-toolkit -> cloneDeep()) that do. Here I learned that the Web API has introduced this function as well!

The Web API - for those unfamiliar with it - is built directly into your browser exposing lots of useful functions to your JavaScript application. The structuredClone() function was first introduced into the HTML DOM API for Deno 1.14 and Node.js 17.0.0 by October 2021 and has since become supported by all major browsers around February 2022.

So in a way structuredClone() now IS “natively" supported and it should save you some hassle going forward. No more JSON.parse(JSON.stringify())!!

Further reading

https://developer.mozilla.org/en-US/docs/Web/API/structuredClone https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction#what_are_apis

Photo of Louis la Grange

Louis la Grange

Software Engineer