Deep cloning nested objects natively without using lodash
•6 min read•••• views
•6 min read•••• views
Did you know that you could easily clone deeply nested objects in JavaScript without using lodash, all just by using a single function ?
Yes, structuredClone() function lets you do just that and it's built into the Web APIs.
const currentUser = {
full_name: {
first_name: 'Anil',
last_name: 'Seervi'
},
joined_at: new Date(0),
languages: ['English', 'JavaScript']
};
const cloneUser = structuredClone(currentUser);In the example, you can notice that nested object full_name, Date Object joined_at and the Array languages are all well preserved and cloned into cloneUser variable.
currentUser.full_name; // Object: {first_name: "Anil", last_name: "Seervi"}
currentUser.joined_at; // Date: Thu Jan 01 1970 05:30:00 GMT+0530 (India Standard Time)
currentUser.languages; // Array: ["English", "JavaScript"]That's right, structuredClone can clone many other types of Objects. We'll discuss all the other types and also it's restrictions in the following sections. But before that let's have a look at the syntax of structuredClone.
structuredClone(value);
structuredClone(value, options);value : The object to be cloned. This can be any structured-cloneable type.
options(optional) :
An object with the following properties:
transfer : An array of transferable objects that will be moved rather than cloned to the returned object.
The returned value is a deep copy of the original value.
DataCloneError DOMException
Thrown if any part of the input value is not serializable.
const multipleTypesObject = {
set: new Set([1, 3, 2]),
map: new Map([[3, 2]]),
regex: /foobar/,
deep: { array: [{ file: new File(someBlobData, 'file.txt') }] },
error: new Error('Hello!')
};
multipleTypesObject.circular = multipleTypesObject;
const fullyCloned = structuredClone(multipleTypesObject);
// ✅ All good, fully and deeply copied!structuredClone can not only clone Primitive types, additionally it can also:
Date, Set, Map, Error, RegExp, ArrayBuffer, Blob, File, ImageData, and many moreUsing structuredClone to deep copy an Object will also preserve any circular references in that object. Have a look at the example below where itself preserves it's reference to the parent Object.
// Create an object with a value and a circular reference to itself.
const user = { name: 'Anil' };
user.itself = user;
// Clone it
const clone = structuredClone(user);
console.assert(clone !== user); // the objects are not the same (not same identity)
console.assert(clone.name === 'Anil'); // they do have the same values
console.assert(clone.itself === clone); // and the circular reference is preservedApart from deeply cloning objects, you can also transfer certain Transferable objects from original object to the cloned object, using the transfer property of the options parameter. Transferring makes the original object unusable.
// 16MB = 1024 * 1024 * 16
const uInt8Array = Uint8Array.from({ length: 1024 * 1024 * 16 }, (v, i) => i);
console.log(uInt8Array.byteLength); // 16777216
const transferred = structuredClone(uInt8Array, {
transfer: [uInt8Array.buffer]
});
console.log(uInt8Array.byteLength); // 0, because it was buffer was transferredIt is important to note that spreading the object will only make a shallow copy and not a deep copy.
const currentUser = {
full_name: {
first_name: 'Anil',
last_name: 'Seervi'
},
joined_at: new Date(0),
languages: ['English', 'JavaScript']
};
const spreadObject = {
...currentUser,
full_name: { ...currentUser.full_name }
};
// 🚩 oops - we just added "CSS" to both the copy *and* the original array
spreadObject.languages.push('CSS');
// 🚩 oops - we just updated the date for the copy *and* original date
spreadObject.joined_at.setTime(969);Which means that nested objects will just share the same references and updating one will also update the other.
JSON.parse(JSON.stringify(x))?So you happen to know this trick, its fast and has gotten the job done for you, but do you also know what are its shortcomings ?
Let's take an example to understand that :
const event = {
title: 'Pusblish new article',
date: new Date('4/1/2023')
};
// 🚩 JSON.stringify converted the `date` to a string
const wrongEvent = JSON.parse(JSON.stringify(event));
console.log(wrongEvent);
/*
{
title: "Publish new article",
date: "2023-03-31T18:30:00.000Z"
}
*/So you see in this example how date which was supposed to be a Date object was converted to a string.
There are more types of objects that JSON.parse(JSON.stringify(x)) can't convert properly. Let's use the structured cloneable types example to see what output we get.
const multipleTypesObject = {
set: new Set([1, 3, 2]),
map: new Map([[3, 2]]),
regex: /foobar/,
deep: { array: [{ file: new File(someBlobData, 'file.txt') }] },
error: new Error('Hello!')
};
const totallyWrongCopy = JSON.parse(JSON.stringify(multipleTypesObject));If we try logging totallyWrongCopy we would get :
{
"set": {},
"map": {},
"regex": {},
"deep": {
"array": [
{ file:{}}
]
},
"error": {},
}Where as using a structuredClone would have cloned everything just fine. One more thing to note is that you cannot JSON.stringify a circular object.
structuredClone not clone ?Function objects cannot be duplicated by the structured clone algorithm; attempting to throws a DataCloneError exception.DataCloneError exception.lastIndex property of RegExp objects is not preserved.// Throws DataCloneError
structuredClone({ fn: () => {} });
// Throws DataCloneError
structuredClone({ el: document.body });
structuredClone({
get foo() {
return 'bar';
}
});
// Becomes: { foo: 'bar' }
// 🚩 Object prototypes
class MyClass {
foo = 'bar';
myMethod() {
/* ... */
}
}
const myClass = new MyClass();
const cloned = structuredClone(myClass);
// Becomes: { foo: 'bar' }
cloned instanceof myClass; // falseMore simply put, anything not in the below list cannot be cloned:
Array, ArrayBuffer, Boolean, DataView, Date, Error types (those specifically listed below), Map , Object but only plain objects (e.g. from object literals), Primitive types, except symbol (aka number, string, null, undefined, boolean, BigInt), RegExp, Set, TypedArray
Error, EvalError, RangeError, ReferenceError , SyntaxError, TypeError, URIError
AudioData, Blob, CryptoKey, DOMException, DOMMatrix, DOMMatrixReadOnly, DOMPoint, DomQuad, DomRect, File, FileList, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemHandle, ImageBitmap, ImageData, RTCCertificate, VideoFrame
structuredCloneApart from the availability in workers, structuredClone has pretty good support in all major browsers and runtimes.
