structuredClone: deep copy objects in JavaScript
The spread operator copies only one level, and the JSON trick loses dates and breaks on cycles. structuredClone makes a true deep copy with one built-in call.
Copying an object in JavaScript looks simple and hides a trap. The spread operator copies only the first level. Nested objects are still shared.
const original = { user: "Rafi", address: { city: "Dhaka" } };
const shallow = { ...original };
shallow.address.city = "Chattogram";
console.log(original.address.city); // "Chattogram": the nested object is shared
Change the copy and the original changes with it. Bugs of this kind are hard to trace, because the line that breaks is far from the line that caused it.
The built-in fix
const deep = structuredClone(original);
deep.address.city = "Sylhet";
console.log(original.address.city); // "Dhaka": the original is untouched
structuredClone() returns a full copy with no shared references, however deep the object goes. It is a global function in all current browsers and in Node.js 17 and later. There is nothing to install.
Better than the JSON trick
For years the common workaround was JSON.parse(JSON.stringify(value)). It works for plain data and fails quietly for everything else.
| Value | JSON round trip | structuredClone |
|---|---|---|
Date |
Becomes a string | Stays a Date |
Map and Set |
Become empty objects | Copied correctly |
undefined |
Dropped | Kept |
NaN and Infinity |
Become null |
Kept |
BigInt |
Throws | Copied |
| Circular reference | Throws | Copied correctly |
Typed arrays and ArrayBuffer |
Mangled | Copied |
const order = { placedAt: new Date(), tags: new Set(["gift"]) };
order.self = order; // a circular reference
const copy = structuredClone(order);
copy.placedAt instanceof Date; // true
copy.tags.has("gift"); // true
copy.self === copy; // true
What it cannot copy
- Functions. An object with a function property throws a
DataCloneError. - DOM nodes. Use
node.cloneNode(true)for those. - Class instances. The data is copied, but the result is a plain object. The prototype and its methods are gone.
- Getters and setters. The getter runs and its current value is copied.
If your objects carry methods, write a clone() method on the class instead.
Moving instead of copying
For large binary data, copying is wasteful. The transfer option hands the memory over without duplicating it.
const buffer = new ArrayBuffer(1024 * 1024);
const moved = structuredClone(buffer, { transfer: [buffer] });
// buffer is now empty, and moved owns the memory
When to use it
- before changing data you received from somewhere else,
- to keep a snapshot for undo or for comparing later,
- when preparing a draft of a form that the user may cancel.
One caution: a deep copy visits every value. Cloning a very large object inside a loop or on every key press will cost you. Clone at the boundaries, not everywhere.
The takeaway
Reach for the spread operator when one level is enough, and for structuredClone when the data is nested. The JSON trick can be retired.