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 thingsJSON.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
| Type | JSON.parse(JSON.stringify()) | structuredClone() |
|---|---|---|
Date | Converted to ISO string | Preserved as Date |
Map / Set | Becomes {}, data lost | Preserved correctly |
undefined property | Dropped entirely | Preserved |
| Circular reference | Throws TypeError | Handled correctly |
RegExp | Becomes {} | Preserved |
| Function | Silently dropped | Throws DataCloneError |
Typed arrays (Uint8Array, etc.) | Corrupted to a plain object | Preserved 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.