JSON Merge
Deep merge, shallow merge, or concat two JSON objects or arrays instantly.
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
| Mode | Nested Objects | Arrays | Scalar Conflicts |
|---|---|---|---|
| Deep Merge | Recursively merged key-by-key | B replaces A | B wins |
| Shallow Merge | B replaces A wholesale | B replaces A | B wins |
| Array Concat | Recursively merged key-by-key | [...A, ...B] concatenated | B 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):
{
"app": { "name": "MyApp", "version": "1.0" },
"settings": { "theme": "dark", "debug": false }
}JSON B (override):
{
"settings": { "debug": true, "language": "en" }
}Deep merge result:
{
"app": { "name": "MyApp", "version": "1.0" },
"settings": { "theme": "dark", "debug": true, "language": "en" }
}Where JSON Merge Helps
- ▸Layered configuration — Combine a base config with an environment-specific override file (dev/staging/prod) without hand-editing the merged result.
- ▸Applying a PATCH update — Merge a partial update payload into an existing record to see the resulting document before sending it to an API.
- ▸Combining default + user settings — Merge application defaults with a user's saved preferences, keeping any field the user hasn't overridden.
- ▸Reconciling two data exports — Merge fields captured from two different sources into a single unified record.
Deep Merge in Code
JavaScript:
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:
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 out3-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.