structuredClone: native way to deep clone in JS

Last updated on January 2023

Summary of how you can deep clone easily with structuredClone

tldr: you can use structuredClone(someObject) and it will deep clone (recursively) it. Works with all modern browsers.

Example of deep cloning with structuredClone:

const people = {
  names: ["fred", "bart"],
};

const deepCloned = structuredClone(people);

// remove bart from the original object
people.names.pop();

// add 1 item to the deepCloned object:
deepCloned.names.push("spongebob");

console.log(people.names); // ['fred']
console.log(deepCloned.names); // ['fred', 'bart', 'spongebob']

Deep cloning the 'old' or normal way in JS

In Javascript you can clone an object like this:

const objectA = {name: "fred", age: 50}
const objectB = {...objectA}
console.assert(objectA !== objectB)

But any nested objects won't be cloned:

const objectA = {name: "fred", pets: ['cat','dog']}
const objectB = {...objectA}
objectA.pets.push('bird')
console.assert(objectA.length === 3)
console.assert(objectB.length === 2) // false! 

So often we will use JSON.parse(JSON.stringify(objectA))

This works, its fast too. But it won't clone things like dates correctly.

const objectA = {when: new Date('1999-12-31')}
const objectB = JSON.parse(JSON.stringify(objectA))

console.assert(typeof objectA.when === 'object')
console.assert(typeof objectB.when === 'object') // false! it gets turned into a string

Other issues with JSON.parse(JSON.stringify(...)) include:

  • you cannot use it when there are circular references.
  • if you pass in types it cannot stringify (like functions, undefined) it will silently ignore those.

So if we're cloning objects/arrays with non primitive objects then we often use a library, such as lodash's cloneDeep() to recurisvely clone a variable.

But there is something built into JS APIs now to deep clone!

structuredClone() is now available in JS (on the web api - node also has an api for it) to deep clone an object.

It will recursively clone objects (of most types), and handles circular references.

const objectA = {when: new Date('1999-12-31')}
const objectC = window.structuredClone(objectA);

console.assert(typeof objectA.when === 'object')
console.assert(typeof objectC.when === 'object')
console.assert(objectA.when !== objectC.when) 

Apart from the edge cases of object types it cannot clone (see section below), it behaves exactly how you would expect a deep clone function to work. Pass in an object or array, and all its properties (recursively) will be cloned.

What will structuredClone work on

It works on most things (see next section though to see what it cannot be used for).

Some cherry picked examples:

  • plain old javascript objects
  • Array / ArrayBuffer
  • Boolean, String objects.
  • All primitives except symbol
  • RegExp (see note below on lastIndex)
  • Set and Map objects
  • Date
  • some error objects (see note below)
  • It can also clone a lot of Web/API types such as Blob, DOMRect, FileList, ImageData etc.

What structuredClone cannot be used for

There are some things that structuredClone cannot be used for.

  • you cannot clone symbols
  • you cannot clone classes (but can clone instances of those classes)
  • you cannot clone functions (function expressions or arrow functions). DataCloneError error is thrown
  • you cannot clone DOM nodes (this also throws a DataCloneError error)
  • prototype chain is not cloned/duplicated
  • lastIndex property of RegExp is always set to 0 if you try to clone a RegExp
  • property descriptors, setters and getters are also not duplicated. So if you try to clone an object with readonly properties, the cloned version property will be read+write.
  • There are some restrictions on cloning errors - if the Error name is not Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, then it will be set to Error.

A note on cloning classes:

class SomeClass {
    greeting = 'Hello stranger'
    constructor(name) {
        this.greeting = `Hello ${name}`
    }
}
structuredClone(SomeClass) // << throws an error
structuredClone(new SomeClass("fred")) // << works ok as we are passing in an instance of the class

And as you cannot clone functions, this won't work:

const validators = {
    isEmail: (val) => val.includes('@')
}
structuredClone(validators) // << throws an error

How to use structuredClone()

It takes two arguments:

  • First is required, it is the object to clone
  • The second is optional and is used to transfer certain objects instead of cloning.

How the transfer works

I've never seen it in use, but you can transfer the object instead of cloning it. doing this will make the original data unusable. See the example:

// 16MB = 1024 * 1024 * 16
const uInt8Array = Uint8Array.from({ length: 1024 * 1024 * 16 }, (v, i) => i);

console.log(uInt8Array.byteLength) // 16777216

const cloneTransferred = structuredClone(uInt8Array, {
    transfer: [uInt8Array.buffer],
});
console.log(uInt8Array.byteLength) // original is now 0
console.log(cloneTransferred.byteLength) // 16777216

This is useful when validating data in a buffer before saving it. Instead of copying the data, you can transfer the data. Any changes to the original data will fail, ensuring that you can be sure it wasn't misused.

Example of deep cloning a circular reference with structuredClone()

You can use structuredClone with circular references (and it will point to the original object)

const person = { name: "fred" };
person.itself = person; // << circular reference

const clone = structuredClone(person);

console.assert(clone !== person); // different objects
console.assert(clone.name === "fred"); // cloned values
console.assert(clone.itself === person); // circular  reference points at original object

Where to find out more

  • I haven't really seen it used in many places. I think due to its lack of good support until recently. (since Chrome 98, FF 94, Safari 15.4). Was added to node in v17. I'd love to find out this is in use in more places
  • See specs here
  • MDN
  • Lodash clone deep for an alternative
  • Check browser support. It is supported in all modern browsers (but not all browsers in workers). Node supports it too. If you are supporting older browsers there is a polyfill.
© 2019-2023 a5h.dev.
All Rights Reserved. Use information found on my site at your own risk.