javascriptdeep-clonestructuredclonejson-stringifyobjectsconcepts

Deep Clone in JavaScript: structuredClone vs JSON.parse(JSON.stringify)

·8 min read·JSON Concepts

Why copying an object is harder than it looks

Assigning an object doesn't copy it — const b = a just makes b another reference to the same object, so mutating b.user.name also changes a. To get an independent copy you need a deep clone: a brand-new object tree with no shared references. For years the go-to trick was a JSON round-trip, and it works just often enough to hide its sharp edges. Modern JavaScript has a proper answer — structuredClone — and knowing the difference prevents a whole class of silent data-corruption bugs.

The JSON round-trip clone

The classic one-liner serializes to a JSON string and parses it back:

js
const copy = JSON.parse(JSON.stringify(original));

Because JSON.stringify walks the whole tree and JSON.parse rebuilds a fresh one, the result is genuinely deep — no shared references. For a plain object of strings, numbers, booleans, arrays, and nested objects, it's perfectly fine and still widely used.

The problem is everything JSON *can't represent*. The round-trip doesn't just fail loudly — it silently changes your data:

  • Dates become strings. JSON.stringify turns a Date into an ISO string, and JSON.parse leaves it as a string. Your clone's createdAt is now text, not a Date — and nothing warned you. (See serializing dates in JSON.)
  • `undefined` and functions vanish. Object properties set to undefined or a function are dropped entirely. A field silently disappears from the clone.
  • `undefined` in arrays becomes `null`. Array holes don't survive as undefined.
  • `Map`, `Set`, `RegExp` are mangled. A Map or Set becomes {}; a RegExp becomes {}. All their contents are lost.
  • `NaN` and `Infinity` become `null`.
  • BigInt throws. JSON.stringify throws on a BigInt outright.
  • Circular references throw. Any object that references itself produces TypeError: Converting circular structure to JSON.

For a config object of primitives, none of this bites. For anything with dates, maps, or cycles, the round-trip quietly corrupts the copy.

structuredClone: the native deep clone

structuredClone is a built-in function (browsers and Node 17+) that deep-clones using the structured clone algorithm — the same mechanism the platform uses to pass data to Web Workers. It exists precisely to fix the JSON round-trip's gaps:

js
const copy = structuredClone(original);

It correctly preserves the types JSON destroys:

  • Dates stay Dates, RegExp stays RegExp.
  • `Map` and `Set` clone with their contents intact.
  • Circular references just work — a self-referencing object is cloned with the cycle preserved, no throw.
  • Typed arrays, `ArrayBuffer`, `Blob`, `File` and many other built-ins are supported.
  • `BigInt`, `NaN`, `Infinity` all survive.

For most real-world objects, structuredClone is simply the correct default now.

What structuredClone still can't do

It's not magic — the structured clone algorithm has its own exclusions, and it *does* throw (a DataCloneError) rather than corrupt silently, which is the better failure mode:

  • Functions can't be cloned — passing an object containing a function throws.
  • DOM nodes (most of them) can't be cloned.
  • Prototypes are not preserved. A class instance is cloned as a plain object with the same properties but not the same prototype chain — so it won't be an instanceof your class.
  • Property descriptors (getters/setters, non-enumerable flags) are not carried over.

Choosing a clone strategy

NeedUse
Plain primitives/arrays/objectsstructuredClone (or the JSON trick if you like)
Dates, Maps, Sets, cycles, typed arrays`structuredClone`
Objects containing functionsA library like lodash cloneDeep, or clone manually
Preserve class instances / prototypesCustom clone, or a class clone() method
Just need a fast shallow copySpread { ...obj } or Object.assign

The short version: default to `structuredClone`, drop to a library like lodash's cloneDeep when you need to clone functions or class instances, and only use JSON.parse(JSON.stringify(x)) when you already know the data is pure JSON-safe primitives. The JSON trick isn't wrong — it's just a clone that quietly assumes your data looks exactly like JSON, which real objects often don't.

For the serialization details behind the round-trip, see the JSON.stringify guide and null vs undefined handling. To inspect a cloned structure, format it.

Frequently asked questions

It only preserves JSON-representable data. Dates turn into strings, undefined and functions are dropped, Map/Set/RegExp are flattened to empty objects, NaN/Infinity become null, BigInt throws, and circular references throw. It's fine for plain primitive data but silently corrupts anything richer.

structuredClone is a built-in JavaScript function (browsers and Node 17+) that deep-clones a value using the structured clone algorithm. It correctly copies Dates, Maps, Sets, typed arrays, and circular references — the cases where the JSON round-trip fails.

No. Cloning an object that contains a function throws a DataCloneError. If you need to clone functions or class instances, use a library like lodash cloneDeep or write a custom clone.

It preserves the data but not the prototype. A class instance is cloned into a plain object with the same properties, so the clone won't be instanceof your class. Use a custom clone method if you need the prototype chain.

Default to structuredClone for most objects. Use a deep-clone library when you must clone functions or preserve class prototypes, and reserve JSON.parse(JSON.stringify(x)) for data you know is pure JSON-safe primitives.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.