MergeJSON

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.

Published February 26, 2026

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.assign and lodash.merge mutate 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 tool

Keep reading