The structuredClone You Should Have Been Using Instead of JSON Hacks

JS Edition

June 22, 2026

At some point, every JavaScript developer writes this:

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

You know it’s ugly. I know it’s ugly. We all wrote it anyway because it worked — until it didn’t.

The thing is, JSON.parse(JSON.stringify()) isn’t a deep clone. It’s a JSON round-trip that happens to look like a deep clone. Those are different things, and the gap between them will eventually bite you in production.

Here’s what silently disappears when you run your object through JSON:

const original = {\n  name: "Sri",\n  dob: new Date("1990-01-01"),\n  greet: () => "hello",\n  score: undefined,\n  ref: document.querySelector("body"),\n};\n\nconst copy = JSON.parse(JSON.stringify(original));\n\nconsole.log(copy.dob);     // "1990-01-01" — a string, not a Date\nconsole.log(copy.greet);   // undefined — the function is gone\nconsole.log(copy.score);   // undefined — but it's not even in the object\nconsole.log(copy.ref);     // {} — a DOM node became an empty object

Your Date became a string. Your functions evaporated. Your undefined values ghosted. Your DOM nodes turned into empty objects. No errors. No warnings. Just silently wrong data.

And if you have circular references anywhere? It throws:

const a = {};\na.self = a;\n\nJSON.parse(JSON.stringify(a)); // TypeError: Converting circular structure to JSON

So the thing we all reached for as the “safe” deep clone is actually pretty unsafe.


Enter structuredClone

Shipped natively in browsers in 2022, structuredClone is the actual deep clone API the platform always should have had:

const copy = structuredClone(original);

That’s it. No import, no library, no npm install. It handles what JSON chokes on:

const original = {\n  dob: new Date("1990-01-01"),\n  scores: new Map([["math", 95]]),\n  flags: new Set([1, 2, 3]),\n  buffer: new ArrayBuffer(8),\n};\n\nconst copy = structuredClone(original);\n\nconsole.log(copy.dob instanceof Date);   // true\nconsole.log(copy.scores instanceof Map); // true\nconsole.log(copy.flags instanceof Set);  // true

Dates stay Dates. Maps stay Maps. Sets stay Sets. Typed arrays, ArrayBuffers, RegExp — all handled correctly. Circular references work too:

const a = {};\na.self = a;\n\nconst copy = structuredClone(a);\nconsole.log(copy.self === copy); // true

What it still won’t clone

structuredClone uses the Structured Clone Algorithm, which is powerful but not unlimited. A few things it explicitly refuses:

// Functions — will throw\nstructuredClone({ fn: () => {} }); // DataCloneError\n\n// DOM nodes — will throw\nstructuredClone({ el: document.body }); // DataCloneError\n\n// Class instances lose their prototype chain\nclass User {\n  greet() { return "hi"; }\n}\nconst u = new User();\nconst copy = structuredClone(u);\ncopy.greet(); // TypeError: copy.greet is not a function

Functions are not cloneable by design — they’re not data, they’re behaviour. DOM nodes can’t be structurally cloned because they carry too much browser state. And class instances get cloned as plain objects, so the prototype chain is gone.

If your data is plain objects with nested arrays, dates, maps, and sets — structuredClone is perfect. If you’re cloning class instances with methods, you need a different approach.


The transferable trick

There’s one more thing structuredClone can do that nothing else can — transfer ownership of data instead of copying it:

const buffer = new ArrayBuffer(1024);\nconst copy = structuredClone(buffer, { transfer: [buffer] });\n\n// buffer is now detached — zero-copy move, not a clone\nconsole.log(buffer.byteLength); // 0

This is primarily useful when moving large binary data to a Web Worker. Instead of copying megabytes of data across the thread boundary, you transfer ownership of it. The original becomes unusable, and the worker gets the memory directly. If you’re doing anything with image processing, audio, or large datasets in workers, this matters.


Should you replace every JSON.parse(JSON.stringify()) today?

Probably not a big refactor. But new code? Yes, default to structuredClone. It’s supported in every major browser since 2022 and in Node since v17. The only reason to reach for JSON round-tripping now is if you specifically need serializable output — like sending data over the wire or saving to localStorage. In that case JSON is the right tool. For everything else, structuredClone is what you actually want.

The JSON trick was never a deep clone. It was a workaround that happened to work often enough that we stopped questioning it.


Thanks for reading Under The Hood! This post is public so feel free to share it.

Share

Leave a comment