# structuredClone vs JSON.parse(JSON.stringify()) for Deep Copying in Node.js

I copied a config object the way everyone does it. `JSON.parse(JSON.stringify(obj))`, one line, done. The object had a `Date` in it and a `Map`. The copy came back, the code ran, and half an hour later something downstream called `.get()` on what used to be a Map and died. The Map was now `{}`. The Date was now a string. Nothing had thrown. That is the whole problem with the JSON trick: it fails silently, in the copy, and the error shows up somewhere else entirely.

So I stopped guessing and measured both approaches. Same machine, Node v24.22.1, same object shape at three sizes: 10 items (653 bytes as JSON), 1,000 items (61,380 bytes), 50,000 items (3,499,212 bytes). Each object had a nested `Date`, a `Map`, and a few levels of nesting, because a benchmark on plain `{a: 1}` tells you nothing about real data. I ran each copy 200 times at the small size, 100 at medium, 20 at large, and took the median.

| Object size | JSON round-trip | structuredClone | Winner |
|---|---|---|---|
| 653 B (10 items) | 0.012 ms | 0.017 ms | JSON, by a hair |
| 61,380 B (1,000 items) | 0.792 ms | 0.956 ms | JSON, ~17% faster |
| 3,499,212 B (50,000 items) | 48.162 ms | 56.441 ms | JSON, ~15% faster |

JSON wins on speed at every size, and the gap stays roughly proportional. If your data is plain JSON-shaped stuff — strings, numbers, booleans, arrays, plain objects, null — the old trick is still the faster copy. That is a real result, and it is also the least interesting part of the test.

The interesting part is what each method does to types that are not JSON types. I ran both over one type at a time:

- **Date:** JSON turns it into an ISO string. `instanceof Date` comes back `false`. structuredClone hands you a real `Date` back.
- **Map:** JSON gives you `{}`. Empty object, entries gone, no error. structuredClone gives you a working `Map`, `.get("k")` returns `1`.
- **RegExp:** JSON gives you `{}`. structuredClone gives you a real `RegExp`.
- **undefined as a value:** JSON drops the key entirely. structuredClone keeps the key, value `undefined`.
- **Function:** JSON drops the key and carries on. structuredClone throws `DataCloneError` and copies nothing at all.
- **BigInt:** `JSON.stringify` throws `TypeError: Do not know how to serialize a BigInt`. structuredClone copies `10n` fine.
- **Circular reference:** `JSON.stringify` throws `TypeError`. structuredClone handles it, and the copy's `self` points at the copy itself (`copy.self === copy` is `true`).

Read that list again, because it splits into two very different kinds of failure. JSON mostly fails quiet: it drops keys, empties Maps, flattens Dates to strings, and hands you something that looks fine until you use it. structuredClone mostly fails loud: a function in the object means the whole copy throws. Loud is better. A throw at the copy site tells you where the problem is. A string where a Date should be tells you nothing until a `.getTime()` call three modules away blows up on a Tuesday.

There is one more habit worth dropping. A lot of codebases copy with JSON because "structuredClone is slower" and then never check the assumption. On my numbers, the gap is about 15% on a 3.5 MB object, and under a millisecond on anything smaller. If you are copying objects that big in a hot loop, the copy method is not your problem — the fact that you are copying 3.5 MB objects in a hot loop is. For ordinary sizes, pick by correctness, not by 0.005 ms.

My rule now, and it has held up since the Map incident:

- Data came from JSON, or is going back to JSON? Use the JSON round-trip. It is faster, and nothing can get mangled, because there was nothing non-JSON in it to begin with.
- Data has Dates, Maps, Sets, RegExps, typed arrays, or circular references? Use structuredClone. The 15% is cheap insurance against a silent type change.
- Data might contain functions? Neither method copies them. JSON drops them quietly, structuredClone refuses. If you need functions in the copy, you need a custom clone, not either of these.

One last detail for the skeptics: the benchmark object itself contained a `Date` and a `Map`, and the timings above include whatever each method did with them. JSON was still faster while doing less work — it serializes a Date to a string and a Map to `{}`, which is less work than cloning them properly. The speed gap is partly a correctness gap. You are not comparing two ways of doing the same thing. You are comparing a fast lossy copy with a slower faithful one, and the right pick depends on whether your data can afford to lose anything.

*P.S. Everything I write stays free — that part doesn't change. But if this one was worth something to you, you can buy me a coffee ☕ (one-time or monthly, both work). It covers the API credits and hosting behind these experiments — and honestly, it's also the nudge that gets the next one started on the evenings I'd otherwise skip it. — Salam*

[![Buy me a coffee](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/87bycvv0drlpbwxjppyi.png)](https://www.patreon.com/c/AheadKit)
