How to Merge JSON Objects in JavaScript (Spread, Assign & Deep Merge)
Merge JSON objects in JavaScript the right way. Compare the spread operator, Object.assign, and a proper recursive deep merge — with code and gotchas.
JavaScript gives you several ways to merge objects, and they behave differently in ways that cause real bugs. This tutorial covers the spread operator, Object.assign, and a correct recursive deep merge — with the gotchas that bite people in production.
Parsing first
Remember that JSON is text. Before you can merge, you parse it into objects:
const a = JSON.parse(jsonStringA);
const b = JSON.parse(jsonStringB);
And after merging you serialize back:
const out = JSON.stringify(merged, null, 2);
Everything below operates on the parsed objects.
The spread operator
The modern, idiomatic shallow merge:
const merged = { ...a, ...b };
Properties from b override those from a. Clean and fast — but shallow. Nested objects from b replace those in a entirely rather than combining.
Object.assign
The older equivalent does the same shallow merge, but mutates its first argument:
Object.assign(a, b); // a is now modified!
Always pass a fresh target to avoid surprises:
const merged = Object.assign({}, a, b);
Both spread and Object.assign are shallow, so neither is safe for nested data.
The shallow-merge trap
Consider:
const a = { user: { name: "Ada", role: "admin" } };
const b = { user: { name: "Grace" } };
const merged = { ...a, ...b };
// → { user: { name: "Grace" } } ← role is gone!
The entire user object was replaced, silently dropping role. This is the number-one merge bug in JavaScript. When your data is nested, you need a deep merge.
A correct deep merge
function isObject(v) {
return v !== null && typeof v === "object" && !Array.isArray(v);
}
function deepMerge(a, b) {
const out = { ...a };
for (const key of Object.keys(b)) {
if (isObject(a[key]) && isObject(b[key])) {
out[key] = deepMerge(a[key], b[key]);
} else {
out[key] = b[key];
}
}
return out;
}
Now nested objects combine instead of clobbering:
deepMerge(
{ user: { name: "Ada", role: "admin" } },
{ user: { name: "Grace" } }
);
// → { user: { name: "Grace", role: "admin" } }
What about arrays?
The function above replaces arrays (the else branch). That is often not what you want. To concatenate instead:
if (Array.isArray(a[key]) && Array.isArray(b[key])) {
out[key] = [...a[key], ...b[key]];
}
For union, replace, or merge-by-key behaviour, see our guide to merging JSON arrays. There is no single right answer — it depends on whether your arrays are sets, streams, or keyed records.
Use a library
For production code, reach for a tested implementation rather than maintaining your own:
lodash.merge— recursive deep merge, mutates the destination.deepmerge— small, immutable, with a configurable array merge function.
import deepmerge from "deepmerge";
const merged = deepmerge(a, b);
Gotchas to remember
- Mutation:
Object.assignandlodash.mergemutate their target. Pass{}as the destination if you want a pure result. - Prototype pollution: when merging untrusted input, guard against
__proto__keys — many libraries now do this, but homegrown merges often do not. - Order matters: the last source wins on conflicts. Reorder your inputs to change precedence.
No-code alternative
If you just need to merge a couple of JSON objects once — not wire it into an app — paste them into our browser merge tool. It performs the same deep/shallow and array strategies described here, validates your input, and lets you copy or download the result. For one-off merges it is faster than writing and running a script.
Ready to merge your JSON?
Combine files or snippets in your browser — free and private.
Open the merge toolKeep reading