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.
  • lastIndex on a RegExp, DOM nodes, and the full list of supported types. For those, MDN's "Structured clone algorithm" page says functions and DOM nodes throw DataCloneError, lastIndex is 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.