Fuller University  ·  School of Computing Friday, August 7, 2026
The Language

Stop reaching for JSON.parse(JSON.stringify()) — structuredClone is right there

JSON.parse(JSON.stringify(x)) silently drops Dates, Maps and undefined. structuredClone keeps them, ships everywhere, and costs nothing extra.

The bug arrived as a screenshot: an order form where the delivery date showed up as a string of digits, but only after the user edited a line item. Everything worked on first render. Everything broke on the second. The cause was one line of copying code, written years earlier and copied into the project from a blog post that had itself copied it from somewhere else:
const draft = JSON.parse(JSON.stringify(order));
That expression is the most widely deployed deep-copy implementation in JavaScript, and it is lossy in ways that stay invisible until they aren't. There has been a real alternative in every major browser and in Node for several years now, and most codebases still haven't reached for it.

What the round trip throws away

Serialising to JSON and parsing it back is not a copy operation. It is a conversion to a data format that supports six types, followed by a conversion back. Anything the format cannot express is either transformed into something else or dropped without warning.
Value After JSON round trip After structuredClone
Date ISO string Date
Map / Set Empty object Map / Set
undefined property Key removed Key preserved
NaN / Infinity null NaN / Infinity
BigInt Throws BigInt
RegExp Empty object RegExp
Circular reference Throws Preserved
Typed array Plain object of indices Typed array
Read that table as a list of bugs waiting for the right input. The date column is the one that bites most often, because new Date(isoString) is such a common repair that people stop noticing they are performing it. The undefined row is the sneakiest: a key that vanishes changes the shape of an object, which changes how in checks, destructuring defaults, and spread merges behave downstream.

What structuredClone does instead

The structured clone algorithm has been part of the platform for a long time — it is the mechanism behind postMessage, IndexedDB storage, and worker communication. What changed is that it became directly callable:
const draft = structuredClone(order);
Because it was designed to move live values between JavaScript realms rather than to produce text, it understands the object graph. Dates stay dates. Maps stay maps. A node that appears twice in the source appears once in the copy, referenced twice, exactly as it was. Cycles are handled rather than thrown at. It is also available essentially everywhere you are likely to run code: current browsers, Node, Deno, and Bun all expose it as a global. There is no import, no dependency, and no bundle cost.

Where it still refuses

Structured cloning is not magic, and the places it stops are worth memorising, because it throws rather than silently degrading — which is the behaviour you want.
  • Functions and class methods. Anything callable is not clonable. This is not a limitation to work around; a function closing over a scope cannot meaningfully exist in a copy.
  • DOM nodes. Use node.cloneNode(true) for those.
  • Prototypes. An instance of your Order class comes back as a plain object carrying the same own properties. The data survives; the identity does not.
  • Property descriptors. Getters are evaluated and stored as plain values, and non-enumerable properties are skipped.
The prototype rule is the one that trips teams migrating away from JSON, because JSON dropped prototypes too — so nothing changes — but people expect a "real" clone to preserve the class. If you need instances back, keep a static factory on the class and call it on the cloned data. That is clearer than any clever alternative, and it puts the reconstruction rules where a reader will look for them.

A note on speed, since someone always asks

The instinct is that a native call must be faster than serialising to a string and parsing it back. In our own measurements on a mid-range laptop, that holds for objects containing dates, maps, or large typed arrays — sometimes by a wide margin, because the JSON path is doing conversion work on every value. For flat objects of short strings and numbers, the two are close enough that the difference disappears into measurement noise. Which means performance is the wrong axis for this decision. Both are fast; only one is correct. Choose on correctness and stop thinking about it.

The rule we teach

If you are copying data you own, in memory, in the same process, use structuredClone. If you are producing text to send over a network or write to disk, use JSON.stringify — that is what it is for, and its constraints are the point. The mistake is not using JSON. The mistake is using a serialiser as a copier and hoping the types you happen to have today are the types you will have next quarter. The order form, for the record, needed a two-character change. The code review that approved the original line was not careless; the expression looks harmless, and it had worked for three years. That is what makes lossy defaults expensive: they buy you time, at interest.

The Weekly Module

One email. Every Friday. Just JavaScript.

The week in the language, the runtimes, and the registry — read by 42,000 developers who’d rather be shipping.