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 onlastIndex
)Set
andMap
objectsDate
- 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
symbol
s - 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 aRegExp
- 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.