Deep Copying Objects with the StructuredClone API
Tags: javascript, programming
Published: 2023-04-23 00:22:34


by [Eni Onyedikachi](https://blog.openreplay.com/authors/eni-onyedikachi)

In Javascript, when an object is stored in a variable, the variable is said to contain the [reference value](https://blog.openreplay.com/javascript-types-and-values-explained/) of the object, meaning that the variable does not store an object in itself, but instead, an identifier that represents the memory location of the object. Unlike primitives, copying objects works differently.

## Shallow Copy vs. Deep Copy
Values can be copied in Javascript in two ways: shallow copy and deep copy.

### Shallow Copy
As per [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy)

>A shallow copy of an object is a copy whose properties share the same references (point to the same underlying values) as those of the source object from which the copy was made. As a result, when you change either the source or the copy, you may cause the other object to change too — and so, you may end up unintentionally causing changes to the source or copy you don't expect.

This simply means when using shallow copy, only the first level of the object is copied; deeper levels will be referenced. If the original changes, the copy also changes.

In Javascript, a shallow copy can be created using the `Object.assign()` method:

```javascript
const theOriginal = {
  someProp: "with a string value",
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true,
  },
};

const theShallowCopy = Object.assign({}, theOriginal);

```

If we add or change a first-level property on the shallow copy(`theShallowCopy`). It will not affect the original copy(`theOriginal`); only the shallow copy where we make the change is affected.

```javascript
theShallowCopy.aNewProp = "a new value";
console.log(theOriginal.aNewProp)
//  logs `undefined`
```

However, when we modify a deeply nested property, both the original and shallow copy get affected.

```javascript
theShallowCopy.anotherProp.aNewProp = "a new value";
console.log(theOriginal.anotherProp.aNewProp) 
//  logs `a new value`
```

This is because the deeply nested property is referenced, not copied.


### Deep Copy
As per [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Deep_copy)

>A deep copy of an object is a copy whose properties do not share the same references (point to the same underlying values) as those of the source object from which the copy was made. As a result, when you change either the source or the copy, you can be assured you're not causing the other object to change too; you won't unintentionally be causing changes to the source or copy you don't expect.

This simply means a deep copy creates a new object with its own set of data separate from the original object. If the original object is changed, the copy will not be affected.

To create a deep copy of an object, we can use the `JSON.parse(JSON.stringify(obj))` method:

```javascript
let theOriginalObject = {
  name: "Mary",
  age: 20,
  address: {
    street: "12 Wall St",
    city: "NY",
    state: "New York",
  },
};

let theDeepCopy = JSON.parse(JSON.stringify(theOriginalObject));
```

If we modify the `address` property of the `theOriginalObject`, the address property of the `theDeepCopy` will not be modified because they are two different objects:

```javascript
theOriginalObject.address.state = "california";
console.log(theDeepCopy.address.state); //Output: "New York"
```

However, It is important to know that deep-copy can also be created using a third-party library like [Lodash](https://lodash.com/docs/#cloneDeep).

## Deep Copying Natively with `StructuredClone()`
The `structuredClone()` is a built-in function for deep-copying Javascript values. It uses the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), which until recently wasn't available to developers and had to be used with workarounds. However, recently updated [HTML spec](https://html.spec.whatwg.org/#safe-passing-of-structured-data) solves this problem by exposing a function called `structuredClone()` that runs the structured clone algorithm, making it easier to deep copy values in Javascript.

Let's create a deep copy of an object using `structuredClone()`.

```javascript
const original = {
  site: "https://blog.openreplay.com/",
  published: new Date(),
  socials: [
    {
      name: "twitter",
      url: "https://twitter.com/openreplay",
    },
    {
      name: "youtube",
      url: "shorturl.at/insT6l", //Subscribe!
    },
  ],
};

const copy = structuredClone(original);

```

This is the entire API. This is simply how to create a full/deep copy of an object using the `structuredClone()`  function.



<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>




## Why Use `structuredClone()`?
Since other options are efficiently performant when it comes to deep-copying values in Javascript, why is it important to use the `structuredClone()` function.

The `structuredClone()` is not only performant but is also supported in all major browsers.

![-](https://i.imgur.com/CFusZDF.png)

From [canIUSE](https://caniuse.com/?search=structuredClone)


### `structuredClone()` vs  `JSON.parse(JSON.stringify(x))` 
The `JSON.parse(JSON.stringify(x))` JSON-based hack was a commonly used solution for deep-copying before `structuredClone()`. It was even [optimized by V8](https://v8.dev/blog/cost-of-javascript-2019#json) due to its popularity. However, It has some shortcomings that `structuredClone()` addresses.

For Example:
```javascript
const Person = {
  name: "John",
  date: new Date("2022-03-25"),
  friends: ["Steve", "Karen"]
}

// JSON.stringify converted the `date` to a string
const buggyCopy = JSON.parse(JSON.stringify(Person))
```

If we log `buggyCopy`, we will get the following:

```javascript
{
    name: "John",
    date: "2022-03-25T00:00:00.000Z",
    friends: Array(2)
}
```
![-](https://i.imgur.com/LRRbGQM.png)

The result is not what is expected, `date` is supposed to be a `Date` object, not a string.

JSON is a format that encodes objects in a string. It uses [serialization](https://developer.mozilla.org/en-US/docs/Glossary/Serialization) to convert an object into that string and deserialization to its inverse operation (convert string -> object).

This is why `JSON.stringify`  can only handle basic objects, arrays, and primitives. It can be hard to predict how it will work with other types. For example, `Date` is converted to `string` and `Set` to `{}`. 

However, this is not the case with `structuredClone()`:

```javascript
const Person = {
  name: "John",
  date: new Date("2022-03-25"),
  friends: ["Steve", "Karen"]
}

const bugfreeCopy = structuredClone(Person)
```

If we log `bugfreeCopy`, we will get the following:

```javascript
{
    name: "John",
    date: Object,
    friends: Array(2)
}
```

![-](https://i.imgur.com/veJaL7j.png)

When we use `structuredClone()`, everything works as expected.

### `structuredClone()` vs  `_.cloneDeep`
Lodash’s [cloneDeep](https://lodash.com/docs/4.17.15#cloneDeep) function is another popular solution used for deep-copying values in Javascript. It's efficient and works as expected—Check out this [codepen](https://codepen.io/myogeshchavan97/pen/vYyqjxg?editors=0012).

However, if the function is imported, it will cost you 5.3K gzipped, or if you add the entire library, it will be 25k gzipped. That’s a lot for just creating deep copies. If we also consider the performance cost of using third-party libraries like Lodash’s `cloneDeep()` function to solve a problem, there is already a native solution for it, and it is better and more performant using the latter.

## Limitations of `structuredClone()`
Though `structuredClone()` addressed most(not all) of the weak points of the `JSON.stringify()` method. It has some limitations that are worth paying attention to.

* Functions can not be copied: If you want to copy an object that contains a function, the `DataCloneError` exception will be thrown.

```javascript
//  Error!
structuredClone({ fn: () => { } })
```
---
* DOM nodes can't be copied: It also throws the `DataCloneError` when you attempt to clone DOM nodes.

```javascript
//  Error!
structuredClone({ element: document.body })
```
----
* Property descriptors, setters, and getters can't be copied.
---
* Prototypes can't be cloned: Structured cloning doesn't duplicate the prototype chain. If you copy an instance of a `Class`, the copied object will no longer be an instance of this `Class`. A plain object is returned in place of the original `Class`.

```javascript
class mainClass { 
  greet = 'hello' 
  Method() { /* ... */ }
}
const instanceClass = new mainClass()

const copied = structuredClone(instanceClass)
// Becomes: { greet: 'hello' }

copied instanceof instanceClass // false
```
----
Check out the complete list of supported types that can be copied on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#javascript_types); anything not on this list can't be copied. 


## Conclusion

Thanks to the arrival of `structuredClone`, we can now deep-copy values in Javascript easily and natively without the use of workarounds. It is best practice to use solutions already available natively to solve problems; in doing so, we embrace a better JS ecosystem.

### Additional Resources

* [MDN—StructuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone)
* [WebDev—StructuredCloned](https://web.dev/structured-clone/)