Stack Patterns ■ Episode 12
You have written this line. You know you have.
const copy = JSON.parse(JSON.stringify(original));
It works. Until it does not. Your Date becomes an
ISO string. Your Map becomes an empty object. Your
undefined properties vanish without warning. Your
RegExp becomes {}. NaN
becomes null. Infinity becomes
null. BigInt throws a
TypeError. Circular references throw a
TypeError. And you discover all of this in
production, because the test data was flat, simple, and
contained none of the types that the real data contained.
For over a decade, JavaScript had no native deep clone. The
JSON.parse(JSON.stringify()) hack was the folk
remedy: available everywhere, effective enough for simple cases,
silently destructive for everything else. Lodash's
cloneDeep
was the professional fix: 17 KB minified, 22 million weekly
downloads, for a function the platform should have provided
from the start.
In 2022, the platform did.
The Pattern
const copy = structuredClone(original);
One line. Zero dependencies. Zero kilobytes shipped to the
client. It handles Date, RegExp,
Map, Set, ArrayBuffer,
Blob, File, CryptoKey,
Error (including stack and cause), circular
references, and over thirty more types. Available in
every browser
since March 2022, in Node.js since v17, in Deno since 1.14.
It has been universally available for over four years.
The History
The
structured clone algorithm
has existed in the HTML specification since approximately 2010.
It was used internally by postMessage() (for
sending objects between windows and workers) and
IndexedDB (for storing objects). For eleven years,
the browser contained a complete, type-aware, cycle-safe deep
cloning implementation and simply refused to let you use it.
Rather like discovering your kitchen has had a dishwasher since
2010 and you have been washing up by hand.
In 2016, Surma filed WHATWG Issue #793 proposing that the algorithm be exposed as a public function. The resulting pull request took three and a half years to review, iterate, and merge. One does admire the pace.
Why It Works
The JSON hack operates by serialising your object to a text representation and parsing it back. This round-trip through JSON means every type that JSON does not support is either silently dropped, silently transformed, or throws an error.
structuredClone operates on the object graph
directly. It walks the structure, preserves types, tracks
references to detect cycles, and produces a genuine copy. No
serialisation round-trip. No type erasure. No silent data
loss.
What It Does Not Do
structuredClone has honest constraints. It throws
a DataCloneError (a DOMException)
when it encounters something it cannot clone:
- Functions: throws immediately. Objects are data; functions are behaviour. The algorithm clones data
- DOM nodes: throws immediately. Cloning a DOM tree is
cloneNode(true), notstructuredClone - Property descriptors: getters and setters are not duplicated. The result is a plain data copy
- Prototype chain: discarded. Class instances become plain objects
- Symbols as keys: silently ignored
These are honest limitations. The JSON hack has many of the
same limitations but communicates them by silently returning
wrong data. structuredClone throws an exception.
One does rather prefer the tool that admits what it cannot do
over the tool that pretends it did.
Performance
For small, simple objects with only JSON-safe types, the JSON
hack can be faster (approximately 6x in microbenchmarks). This
is because JSON.stringify and
JSON.parse are highly optimised V8 built-ins, and
the structured clone algorithm performs additional work (type
checking, cycle tracking) that the JSON path skips.
For complex objects with mixed types,
structuredClone performs comparably or better, and
does not destroy your data in the process. One
benchmark
measured 0.25 seconds for structuredClone versus
1.3 seconds for the JSON hack when processing one million
objects.
The pragmatic assessment: if your object is simple enough that
the JSON hack is faster, it is probably simple enough that a
shallow spread ({ ...obj }) suffices. If you need
a true deep clone, you need structuredClone.
When to Use
Every time you reach for
JSON.parse(JSON.stringify()). Every time you
consider installing lodash for a single function. Every time
you need a true copy of state in a reducer, a cache, a form
snapshot, an undo stack, or a worker message.
Lodash cloneDeep: 17 KB, 22 million weekly
downloads, ten years without an update. Twenty-two million
developers, every week, downloading a package that has not
changed since the platform solved the problem.
structuredClone: 0 KB, built in, maintained by
every browser vendor. One of these is a dependency you chose.
The other is a platform feature you already have.
One does note the difference.
JSON.parse(JSON.stringify()) destroys Date, Map, Set, RegExp, undefined, NaN, and throws on circular references. structuredClone handles all of them. Zero KB. Built in since 2022. 30+ types. The browser had the algorithm since 2010 and would not let you use it. Lodash cloneDeep: 17 KB, 22M downloads/week, unchanged for a decade.