Home → Advanced JSON Merge
Merge JSON objects with control over array handling and conflict resolution.
Merge JSON objects with control over array handling and conflict resolution. This tool runs entirely in your browser — no data is ever sent to a server. Free to use, no account required.
Beyond simple key overwrites, advanced merge provides fine-grained control over how conflicts and arrays are handled at every level of the JSON structure.
Choose how arrays under the same key are combined: replace (right wins entirely), concatenate (both arrays joined end-to-end), or union (joined with duplicates removed). Each strategy serves different use cases.
When both objects have the same scalar key, choose which side wins, or keep both values as an array. This is useful when aggregating data from multiple sources where neither value should be discarded.
Simple merge works for straightforward cases, but advanced merge is necessary when the data being combined requires more sophisticated rules.
Environment-specific configuration files often need to override some keys while merging arrays (like plugin lists or middleware chains). Advanced merge handles both in a single pass.
When combining records from different API endpoints or database queries, advanced merge prevents duplicate entries in arrays while still merging the object fields correctly.
The following examples use the same two input objects and show what each merge strategy produces. Understanding these outputs is the key to choosing the right strategy for your use case.
// Object A (base / left)
{
"name": "Alice",
"role": "user",
"tags": ["javascript", "react"],
"settings": { "theme": "dark", "lang": "en" }
}
// Object B (override / right)
{
"role": "admin",
"tags": ["typescript", "react"],
"settings": { "lang": "fr", "notifications": true }
}
{
"name": "Alice",
"role": "admin",
"tags": ["typescript", "react"],
"settings": { "lang": "fr", "notifications": true }
// ↑ settings from A is completely replaced — "theme" and "lang: en" are lost
}
{
"name": "Alice",
"role": "admin",
"tags": ["typescript", "react"],
"settings": { "theme": "dark", "lang": "fr", "notifications": true }
// ↑ settings is now a deep merge — "theme" is preserved from A, "lang" overridden by B
}
{
"name": "Alice",
"role": "admin",
"tags": ["javascript", "react", "typescript", "react"],
// ↑ both tag arrays joined — contains "react" twice
"settings": { "theme": "dark", "lang": "fr", "notifications": true }
}
{
"name": "Alice",
"role": "admin",
"tags": ["javascript", "react", "typescript"],
// ↑ both arrays joined with duplicates removed — "react" appears only once
"settings": { "theme": "dark", "lang": "fr", "notifications": true }
}
| Strategy | Nested objects | Arrays | Best for |
|---|---|---|---|
| Shallow merge | Right replaces left entirely | Right replaces left | Simple key overrides, no nested data |
| Deep merge + replace | Recursively merged | Right replaces left | Config overrides where arrays are overridden |
| Deep merge + concat | Recursively merged | Both joined | Aggregating lists from multiple sources |
| Deep merge + union | Recursively merged | Joined, deduplicated | Tags, roles, permissions, feature flags |
If you need deep merge in your own code without a library, here is a minimal implementation that handles the most common cases:
function deepMerge(target, source, arrayStrategy = 'replace') {
const result = { ...target };
for (const key of Object.keys(source)) {
if (
source[key] !== null &&
typeof source[key] === 'object' &&
!Array.isArray(source[key]) &&
typeof result[key] === 'object' &&
!Array.isArray(result[key])
) {
// Both values are plain objects — recurse
result[key] = deepMerge(result[key], source[key], arrayStrategy);
} else if (Array.isArray(source[key]) && Array.isArray(result[key])) {
// Both values are arrays — apply strategy
if (arrayStrategy === 'concat') {
result[key] = [...result[key], ...source[key]];
} else if (arrayStrategy === 'union') {
result[key] = [...new Set([...result[key], ...source[key]])];
} else {
result[key] = source[key]; // replace (default)
}
} else {
result[key] = source[key]; // scalar: right wins
}
}
return result;
}
// Usage
const merged = deepMerge(objectA, objectB, 'union');
console.log(JSON.stringify(merged, null, 2));
Each strategy has different behavior for arrays and objects, making it suitable for different use cases. Choose based on whether you want to accumulate, deduplicate, or strictly replace data.
| Strategy | Array Behavior | Object Behavior | Use Case |
|---|---|---|---|
| Replace | Source replaces target | Source properties overwrite | Strict updates, one source of truth |
| Append | Source appended to target | Deep merge | Accumulating lists with full config merge |
| Combine (union) | Deduplicated merge | Deep merge | Tags, categories, feature flags |
| Keep target | Target preserved | Source fills missing keys only | Read-only defaults |
const base = { tags: ["a", "b"], config: { timeout: 30, retries: 3 } };
const update = { tags: ["b", "c"], config: { timeout: 60 } };
// Replace: tags: ["b","c"], config: {timeout:60}
// Append: tags: ["a","b","b","c"], config: {timeout:60, retries:3}
// Union: tags: ["a","b","c"], config: {timeout:60, retries:3}
Real-world scenarios where advanced merge strategies solve common configuration and data problems.
const base = {
database: { host: 'localhost', port: 5432, name: 'myapp' },
redis: { host: 'localhost', port: 6379 },
debug: false
};
const production = {
database: { host: 'db.prod.example.com', name: 'myapp_prod' },
debug: false
};
// Deep merge result: production overrides, base fills missing fields
// database: { host: 'db.prod.example.com', port: 5432, name: 'myapp_prod' }
const roleA = { permissions: ["read", "write"] };
const roleB = { permissions: ["write", "delete"] };
// Union merge → { permissions: ["read", "write", "delete"] }
// No duplicates — "write" appears only once
const defaults = { theme: "light", fontSize: 14, notifications: true };
const userPrefs = { theme: "dark", fontSize: 16 };
// Keep target merge → { theme: "dark", fontSize: 16, notifications: true }
// User's settings are preserved; only missing keys are filled from defaults
Explore more tools: All JSON Tools | Validator | Pretty Print | JSON Diff