Deep Merge vs. Shallow Merge: What's the Difference?
Shallow and deep merges produce very different results for nested JSON. Learn exactly how each works, when to use which, and how to avoid silent data loss.
When you combine two JSON objects, the single most important decision is whether to perform a shallow merge or a deep merge. Choose wrong and you can silently lose data. This article explains the difference with concrete examples and shows when each is the right call.
The setup
Imagine two configuration files. The first sets defaults:
{
"server": { "host": "localhost", "port": 8080 },
"features": { "auth": true }
}
The second provides an override:
{
"server": { "port": 9090 },
"features": { "logging": true }
}
What should the merged result be? It depends entirely on the merge depth.
Shallow merge
A shallow merge only looks at the top-level keys. When both objects have a server key, the second object’s value replaces the first one entirely — it does not look inside.
const merged = { ...a, ...b };
Result:
{
"server": { "port": 9090 },
"features": { "logging": true }
}
Notice what happened: host and the original auth flag vanished. The nested objects were swapped wholesale rather than combined. This is the behaviour of the spread operator, Object.assign, and Python’s {**a, **b}.
Deep merge
A deep merge recurses into nested objects, combining them level by level instead of replacing them:
Result:
{
"server": { "host": "localhost", "port": 9090 },
"features": { "auth": true, "logging": true }
}
Now host and auth survive, port is updated to the new value, and logging is added. This is almost always what you want when layering configuration — defaults plus environment-specific overrides.
A side-by-side summary
| Shallow | Deep | |
|---|---|---|
| Top-level keys | Merged | Merged |
| Nested objects | Replaced wholesale | Merged recursively |
| Risk of data loss | High for nested data | Low |
| Speed | Marginally faster | Negligibly slower |
| Typical use | Flat objects | Config layering, partial updates |
When shallow is actually correct
Deep merge is not always right. Use a shallow merge when:
- your objects are flat (no meaningful nesting),
- a nested value is atomic and should be replaced as a unit — for example, a
coordinates: { lat, lng }pair where a partial update would be meaningless, - you explicitly want later inputs to completely override earlier ones.
The danger is using shallow merge by accident — reaching for the spread operator out of habit and not realizing it clobbered nested data.
What about arrays?
Arrays add a third dimension. Even a “deep” merge has to decide what to do when both objects have an array at the same key: concatenate them, replace one with the other, take the union of unique items, or merge their elements by an id. There is no universally correct answer, which is why a good merge tool makes it a setting. See our dedicated guide on merging JSON arrays for the full breakdown.
Avoiding silent data loss
The reason deep vs. shallow matters so much is that a shallow merge fails silently. There is no error — you just get an object that is missing fields you assumed would be there, and you might not notice until production. Two habits prevent this:
- Default to deep merge for nested data, and only opt into shallow when you have a reason.
- Inspect the result. A tool that shows you key counts and structure after merging makes anomalies obvious.
Try it yourself
Our merge tool lets you flip between deep and shallow with a single dropdown and see the difference instantly — paste the two config examples above and watch which fields survive. Understanding this one distinction will save you from a whole category of subtle bugs.
Ready to merge your JSON?
Combine files or snippets in your browser — free and private.
Open the merge toolKeep reading