structuredClone vs JSON.parse: The Right Way to Deep Copy in JavaScript
Compare structuredClone and JSON.parse(JSON.stringify()) for deep copying, with benchmarks and edge cases.
The Deep Copy Problem
Copying objects in JavaScript is tricky. Spread syntax and Object.assign only create shallow copies — nested objects still share references. For years, developers used \JSON.parse(JSON.stringify(obj))\ as a workaround. Now, \structuredClone()\ is the native solution. But which should you use, and when?
JSON Round-Trip: The Old Way
``javascript
const original = {
name: 'Alice',
scores: [95, 87, 92],
metadata: { created: '2024-01-01' }
};
const copy = JSON.parse(JSON.stringify(original));
copy.scores.push(100);
console.log(original.scores); // [95, 87, 92] — not affected
`
This works for simple objects, but it has serious limitations:
`javascript
// Things JSON round-trip BREAKS:
const problematic = {
date: new Date(), // Becomes a string
regex: /hello/g, // Becomes {}
map: new Map([[1, 2]]), // Becomes {}
set: new Set([1, 2, 3]), // Becomes {}
undef: undefined, // Property is removed entirely
fn: () => 'hello', // Property is removed entirely
inf: Infinity, // Becomes null
nan: NaN // Becomes null
};
const jsonCopy = JSON.parse(JSON.stringify(problematic));
console.log(jsonCopy.date); // "2024-01-01T..." (string, not Date)
console.log(jsonCopy.map); // {}
console.log(jsonCopy.undef); // undefined (property missing)
`
structuredClone: The Modern Way
`javascript
const original = {
date: new Date(),
regex: /hello/g,
map: new Map([[1, 'one'], [2, 'two']]),
set: new Set([1, 2, 3]),
buffer: new ArrayBuffer(8),
nested: { deep: { value: 42 } }
};
const clone = structuredClone(original);
console.log(clone.date instanceof Date); // true console.log(clone.map instanceof Map); // true console.log(clone.set instanceof Set); // true console.log(clone.nested.deep.value); // 42
clone.map.set(3, 'three');
console.log(original.map.has(3)); // false — independent copy
`
What structuredClone Cannot Clone
`javascript
// These will throw DataCloneError:
structuredClone({ fn: () => {} }); // Functions
structuredClone({ el: document.body }); // DOM nodes
structuredClone({ sym: Symbol('x') }); // Symbols
// Workaround: strip non-cloneable properties first function safeClone(obj, keysToOmit = []) { const filtered = Object.fromEntries( Object.entries(obj).filter(([key]) => !keysToOmit.includes(key)) ); return structuredClone(filtered); } ``
When to Use Which
| Feature | JSON round-trip | structuredClone | |---------|----------------|-----------------| | Date | String | Date | | Map/Set | Lost | Preserved | | undefined | Removed | Preserved | | Circular refs | Throws | Handled | | Performance (small) | Faster | Slightly slower | | Performance (large) | Slower | Faster |
Use structuredClone as your default for deep copying. Fall back to JSON round-trip only when you need to serialize to a string anyway, or when working in environments that don't support structuredClone (very old browsers).