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 Datecomes backfalse. structuredClone hands you a realDateback. - Map: JSON gives you
{}. Empty object, entries gone, no error. structuredClone gives you a workingMap,.get("k")returns1. - RegExp: JSON gives you
{}. structuredClone gives you a realRegExp. - 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
DataCloneErrorand copies nothing at all. - BigInt:
JSON.stringifythrowsTypeError: Do not know how to serialize a BigInt. structuredClone copies10nfine. - Circular reference:
JSON.stringifythrowsTypeError. structuredClone handles it, and the copy'sselfpoints at the copy itself (copy.self === copyistrue).
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
Comments
Join the discussionNo comments yet. Be the first to comment.







