JSON.parse(JSON.stringify(x)) and structuredClone(x) are both used to deep copy an object. They do not copy the same things. I ran both on one object in Node v22.23.4 and wrote down the differences.
One object, two copies
Test object: a Date, undefined, NaN, Infinity, a Map, a Set, a RegExp, -0, and an array with a hole ([1, , 3]).
| Value | After JSON round trip | After structuredClone |
|---|---|---|
new Date(0) | the string '1970-01-01T00:00:00.000Z' | a Date |
undefined property | key is gone | undefined, key kept |
NaN, Infinity | null | NaN, Infinity |
new Map([[1, 2]]) | {} | a Map with 1 => 2 |
new Set([1]) | {} | a Set |
/a/gi | {} | /a/gi |
-0 | 0 | -0 |
[1, , 3] | [1, null, 3] | [1, <1 empty item>, 3] |
10n (BigInt) | throws TypeError | 10n |
So the JSON route silently turns a Map into {} and a Date into a string. No error, wrong data.
Where structuredClone throws
structuredClone({ f() {} }); // DataCloneError: f(){} could not be cloned. structuredClone({ s: Symbol("x") }); // DataCloneError: Symbol(x) could not be cloned. A function or a symbol value makes it throw. JSON just drops those keys. If your object has a callback on it, structuredClone fails loudly and JSON hides it.
Classes lose their prototype
class P { constructor() { this.a = 1 } get x() { return 2 } } const c = structuredClone(new P()); c instanceof P; // false c.x; // undefined c; // { a: 1 } The copy is a plain object. Methods and getters from the class are gone. JSON does the same, so neither route gives you back an instance. If you need one, rebuild it from the plain data.
Shared references and cycles
const a = [1]; const c = structuredClone({ x: a, y: a }); c.x === c.y; // true The two properties still point at the same array in the copy. After a JSON round trip they are two separate arrays (c.x === c.y was false). A circular object (o.self = o) clones fine with structuredClone and keeps c.self === c. JSON.stringify throws a TypeError on a cycle.
Getters become plain values
structuredClone({ get g() { return 5 } }) gave a property with value: 5 that is writable, enumerable and configurable. The getter ran once and its result was copied.
What I did not test
- Behavior in browsers. All of the above is Node v22.23.4 only.
-
lastIndexon a RegExp, DOM nodes, and the full list of supported types. For those, MDN's "Structured clone algorithm" page says functions and DOM nodes throwDataCloneError,lastIndexis not preserved, property descriptors, setters and getters are not duplicated, and the prototype chain is not walked. - Performance.
Which one to use
If the data has Dates, Maps, Sets, BigInts, NaN or shared references, use structuredClone. If you need a JSON string to send or store, JSON is the right tool, but expect the table above. If the object has functions or class instances, neither route is a clean copy, so write the copy yourself.
