JSON Merge

Deep merge, shallow merge, or concat two JSON objects or arrays instantly.

Deep Merge: Recursively merge nested objects; B wins on scalar conflicts

What is Deep Merge?

Deep merge combines two JSON objects (A and B) by recursively walking every nesting level. When the same key exists in both objects and both values are plain objects, those objects are merged recursively instead of one simply replacing the other. For any other value type — strings, numbers, arrays, booleans — B's value wins by default.

For example, if A has settings.theme = "dark" and B adds settings.language = "en", a deep merge keeps both — the settings object is not replaced wholesale, it is merged field-by-field.

Merge Modes Compared

ModeNested ObjectsArraysScalar Conflicts
Deep MergeRecursively merged key-by-keyB replaces AB wins
Shallow MergeB replaces A wholesaleB replaces AB wins
Array ConcatRecursively merged key-by-key[...A, ...B] concatenatedB wins

Use Deep Merge when you need to preserve nested fields from both objects (e.g. merging partial config updates). Use Shallow Merge when you only care about top-level keys and B should completely override any nested object from A. Use Array Concat when arrays from both inputs should be combined into a single list rather than replaced.

Example

JSON A (base config):

json
{
  "app": { "name": "MyApp", "version": "1.0" },
  "settings": { "theme": "dark", "debug": false }
}

JSON B (override):

json
{
  "settings": { "debug": true, "language": "en" }
}

Deep merge result:

json
{
  "app": { "name": "MyApp", "version": "1.0" },
  "settings": { "theme": "dark", "debug": true, "language": "en" }
}

Where JSON Merge Helps

  • Layered configurationCombine a base config with an environment-specific override file (dev/staging/prod) without hand-editing the merged result.
  • Applying a PATCH updateMerge a partial update payload into an existing record to see the resulting document before sending it to an API.
  • Combining default + user settingsMerge application defaults with a user's saved preferences, keeping any field the user hasn't overridden.
  • Reconciling two data exportsMerge fields captured from two different sources into a single unified record.

Deep Merge in Code

JavaScript:

js
function deepMerge(a, b) {
  const out = { ...a };
  for (const key of Object.keys(b)) {
    if (isPlainObject(a[key]) && isPlainObject(b[key])) {
      out[key] = deepMerge(a[key], b[key]);
    } else {
      out[key] = b[key]; // B wins for scalars, arrays and mismatched types
    }
  }
  return out;
}
const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);

Python:

python
def deep_merge(a: dict, b: dict) -> dict:
    out = dict(a)
    for key, b_val in b.items():
        a_val = out.get(key)
        if isinstance(a_val, dict) and isinstance(b_val, dict):
            out[key] = deep_merge(a_val, b_val)
        else:
            out[key] = b_val
    return out

3-Way Merge — When Two People Changed the Same Document

The Deep/Shallow/Concat modes above are a 2-way merge: they don't know what either side started from, so B always wins on a conflict. Switch to 3-Way Merge and provide a third input — the common Baseboth Local and Remote started from — and the tool can tell the difference between "only one side changed this" (safe to auto-merge, no conflict) and "both sides changed this to different values" (a real conflict, shown for you to pick Local or Remote).

This is the same idea as a git merge: two branches diverged from a shared commit, and most of the resulting diff merges cleanly — only the lines both branches actually touched need a human decision. Conflicts are listed individually with both candidate values, default to Local until you pick, and the Merged Result updates live as you resolve each one.

Frequently Asked Questions

B (the right/second input) wins for any scalar value, array, or when the types don't match. Only when both sides have a plain object at that key does a deep merge recurse instead of overwriting.

Deep Merge recurses into nested objects and combines them field-by-field. Shallow Merge only looks at top-level keys — if a key holds a nested object in both A and B, B's entire object replaces A's, with no merging inside it.

It merges objects deeply, same as Deep Merge, but for any key where both A and B hold an array, the arrays are concatenated ([...A, ...B]) instead of B's array replacing A's.

No — merging always produces a new object. Both of your original inputs are left unchanged so you can re-run with different modes.

Deep Merge and Shallow Merge expect two objects at the top level. If you need to combine two top-level arrays directly, use Array Concat mode with each array wrapped in an object, or use the JSON to CSV / JSON Sort tools to reshape first.

2-way merge has no idea what changed versus what was always there, so it always lets B win on any conflict. 3-way merge compares Local and Remote against a shared Base, so a field only one side touched merges automatically with no data loss — only fields both sides changed differently are flagged as real conflicts for you to resolve.

Arrays are compared index by index against the base, the same approach used by the JSON Patch generator — correct for detecting per-position changes, but not a full sequence-alignment diff, so an item inserted in the middle of an array on one side can appear as a run of conflicts rather than a single clean insert.

It defaults to the Local value in the Merged Result — the output is always complete and valid JSON, never blocked on pending decisions, so you can copy it at any point and come back to remaining conflicts later if needed.

Related Tools