Skip to content

structuredClone(): Why It Beats the JSON.stringify Trick

What structuredClone() Fixes That the Old Trick Couldn't

structuredClone() is a real deep-copy API, not a faster JSON trick. It handles Dates, Maps, and Sets that JSON.stringify silently breaks.

· · 4 min read

Quick Take

JSON.parse(JSON.stringify(obj)) was never actually a deep clone, it was a deep clone of whatever survives a JSON round trip. Dates, Maps, undefined values, none of them do. structuredClone() is the real thing.

I once shipped a bug where a scheduled task's Date field silently turned into a string after being cloned with JSON.parse(JSON.stringify()), and every downstream comparison against a real Date object quietly failed. That's not an edge case, it's the expected, documented behavior of the JSON round trip. It was never a real deep clone.

TL;DR: structuredClone(), available natively in browsers and Node.js since 2022, is a real deep-copy algorithm, not a JSON round trip. It correctly clones Dates, Maps, Sets, RegExp, typed arrays, and circular references, all things JSON.parse(JSON.stringify()) silently corrupts or drops. It throws explicitly on values it truly can't clone (functions, DOM nodes) instead of silently discarding them.

What the JSON Trick Actually Breaks

const original = {
  createdAt: new Date('2026-01-01'),
  tags: new Set(['a', 'b']),
  metadata: new Map([['key', 'value']]),
  value: undefined,
  callback: () => console.log('hi'),
};

const cloned = JSON.parse(JSON.stringify(original));

console.log(cloned.createdAt);   // "2026-01-01T00:00:00.000Z", a STRING, not a Date
console.log(cloned.tags);        // {}, an empty object, Set data is gone
console.log(cloned.metadata);    // {}, same problem, Map data is gone
console.log('value' in cloned);  // false, undefined properties are dropped entirely
console.log(cloned.callback);    // undefined, dropped silently, no error

None of this throws. None of it warns you. The bug in my scheduled task sat undetected for weeks because everything downstream that treated createdAt as a string happened to work by coincidence, until a comparison against new Date() failed and produced a wrong result instead of a crash.

The Same Object With structuredClone()

const original = {
  createdAt: new Date('2026-01-01'),
  tags: new Set(['a', 'b']),
  metadata: new Map([['key', 'value']]),
  value: undefined,
};

const cloned = structuredClone(original);

console.log(cloned.createdAt instanceof Date); // true
console.log(cloned.tags instanceof Set);        // true, contains 'a', 'b'
console.log(cloned.metadata instanceof Map);    // true, contains 'key' -> 'value'
console.log('value' in cloned);                 // true, undefined is preserved

Every type survives the clone as the correct type, not a JSON-compatible approximation of it. undefined properties are preserved instead of vanishing, which matters for code that checks 'key' in obj rather than obj.key !== undefined.

Circular References: Where JSON.stringify Just Crashes

const node = { name: 'root' };
node.self = node; // circular reference

JSON.stringify(node);
// Uncaught TypeError: Converting circular structure to JSON

const clone = structuredClone(node);
console.log(clone.self === clone); // true, circularity is preserved correctly

Tree structures, linked lists, and any object graph with a parent pointing back to a child are common enough that this isn't a rare edge case. JSON.stringify can't serialize them at all. structuredClone() handles them natively because it clones the object graph directly instead of going through a text format that has no way to represent a cycle.

What structuredClone() Still Can't Clone

try {
  structuredClone({ handler: () => {} });
} catch (e) {
  console.log(e.name); // "DataCloneError"
}

Functions, DOM nodes, and a handful of other types (like WeakMap/WeakSet) genuinely can't be cloned, there's no meaningful way to copy a function's closure or a live DOM node. The difference from JSON.stringify is that structuredClone() throws immediately, telling you the clone failed, rather than silently producing an object missing that property. A loud failure during development beats a quiet one discovered in production.

Old Way vs Modern Way

TypeJSON.parse(JSON.stringify())structuredClone()
DateConverted to ISO stringPreserved as Date
Map / SetBecomes {}, data lostPreserved correctly
undefined propertyDropped entirelyPreserved
Circular referenceThrows TypeErrorHandled correctly
RegExpBecomes {}Preserved
FunctionSilently droppedThrows DataCloneError
Typed arrays (Uint8Array, etc.)Corrupted to a plain objectPreserved correctly

When JSON.stringify Is Still the Right Tool

JSON.stringify isn't obsolete, it does something structuredClone() doesn't: produce a text string, for sending over the network, writing to localStorage, or logging. If you need an actual JSON string as output, that's still the API for it. The mistake was only ever using the stringify-then-parse combination as a substitute for deep cloning in memory, where structuredClone() is both more correct and closer to what the code was trying to express in the first place.

Conclusion

If you're cloning an object that only ever contains strings, numbers, booleans, and plain nested objects or arrays, both approaches give the same result. The moment a Date, Map, Set, or circular reference enters the picture, JSON.parse(JSON.stringify()) produces a silently wrong result, and structuredClone() produces a correct one, natively, with no serialization step in between.

Frequently Asked Questions

Why did JSON.parse(JSON.stringify()) become the default deep-clone pattern?
Before structuredClone() shipped in 2022, JavaScript had no built-in deep-copy function at all. The JSON round trip worked well enough for plain objects made of strings, numbers, booleans, and nested plain objects/arrays, which covers a lot of real-world data, so it became the accepted workaround despite its well-known gaps with Dates, Maps, Sets, and functions.
What does structuredClone() drop that JSON.stringify also drops?
Functions, DOM nodes, and a few other unclonable types are dropped by structuredClone() too, it throws a DataCloneError for these rather than silently discarding them like JSON.stringify does with functions. That's actually an improvement: JSON.stringify silently omits a function property with no warning, while structuredClone() fails loudly, telling you immediately that the value can't be cloned instead of producing a corrupted copy you discover later.
Is structuredClone() slower than the JSON trick?
For plain-object data, they're close, and the difference rarely matters outside a hot loop. structuredClone() is implemented natively in the browser/Node engine using the structured clone algorithm, not by serializing to a string and parsing it back, so for data with Dates, Maps, or typed arrays it's both faster and more correct at the same time, since the JSON version has to work around what it can't represent.