JSON Diff and Patch: Comparing and Merging JSON Documents
When two JSON documents diverge, you need a reliable way to compare, diff, and merge them. This guide covers RFC 6902 JSON Patch, RFC 7396 JSON Merge Patch, deep equality, and practical diffing strategies.
When You Need to Diff JSON
Common scenarios where JSON comparison comes up in real projects:
- Debugging API responses that changed between versions
- Reviewing config file changes in infrastructure-as-code (Terraform state, Kubernetes manifests)
- Implementing optimistic UI updates and conflict resolution
- Audit logs — storing what changed rather than the full document
- Feature flags — sending only the delta to clients instead of the full config
Deep Equality: The Foundation
Shallow equality (===) only compares references for objects. Two objects with identical values are not strictly equal in JavaScript:
const a = { name: "Alice", tags: ["admin"] };
const b = { name: "Alice", tags: ["admin"] };
console.log(a === b); // false — different references
console.log(JSON.stringify(a) === JSON.stringify(b)); // true — but fragile (key order!)
// Robust deep equality — lodash
import { isEqual } from 'lodash';
console.log(isEqual(a, b)); // true ✅
// Or write your own for simple cases:
function deepEqual(a, b) {
if (a === b) return true;
if (typeof a !== 'object' || typeof b !== 'object') return false;
if (a === null || b === null) return false;
const keysA = Object.keys(a), keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every(k => deepEqual(a[k], b[k]));
}⚠️ JSON.stringify comparison breaks when object keys are in different order. Always use a proper deep-equality function.
RFC 6902: JSON Patch
JSON Patch (RFC 6902) is a standard format for describing changes to a JSON document as a sequence of operations. It is used in REST APIs with the PATCH HTTP method and in collaborative editing systems.
| Operation | Example | Effect |
|---|---|---|
| add | { "op":"add", "path":"/tags/0", "value":"vip" } | Insert value at JSON Pointer path |
| remove | { "op":"remove", "path":"/address" } | Delete the value at path |
| replace | { "op":"replace", "path":"/name", "value":"Bob" } | Replace value at path |
| move | { "op":"move", "from":"/a", "path":"/b" } | Move value from one path to another |
| copy | { "op":"copy", "from":"/a", "path":"/b" } | Copy value to a new path |
| test | { "op":"test", "path":"/name", "value":"Alice" } | Assert value equals — abort patch if not |
import jsonpatch from 'fast-json-patch';
const original = { name: "Alice", role: "user", tags: ["active"] };
const modified = { name: "Alice", role: "admin", tags: ["active", "vip"] };
// Generate the patch
const patch = jsonpatch.compare(original, modified);
// [
// { op: 'replace', path: '/role', value: 'admin' },
// { op: 'add', path: '/tags/1', value: 'vip' },
// ]
// Apply the patch to the original
const result = jsonpatch.applyPatch(original, patch).newDocument;RFC 7396: JSON Merge Patch
Simpler than JSON Patch — a merge patch is just a partial JSON document. Keys with non-null values are added or replaced; keys with null values are deleted; missing keys are untouched.
const original = { name: "Alice", role: "user", email: "alice@example.com" };
const mergePatch = { role: "admin", email: null };
// email: null → delete email
// role: "admin" → replace role
// name: missing → keep as-is
// Result:
const result = { name: "Alice", role: "admin" };
// Simple implementation:
function applyMergePatch(target, patch) {
const result = { ...target };
for (const [key, value] of Object.entries(patch)) {
if (value === null) delete result[key];
else if (typeof value === 'object' && !Array.isArray(value))
result[key] = applyMergePatch(result[key] ?? {}, value);
else result[key] = value;
}
return result;
}Limitation: you cannot use JSON Merge Patch to explicitly set a value to null, or to remove items from arrays. For those cases, use JSON Patch (RFC 6902).
Which Approach to Use
| Scenario | Recommended approach |
|---|---|
| Simple field updates, no nulls or array ops | JSON Merge Patch (RFC 7396) — simpler API and payload |
| Complex changes: array inserts, moves, nulls | JSON Patch (RFC 6902) |
| Visual comparison for debugging / code review | A diff tool — show added/removed/changed lines |
| Storing change history in a database | JSON Patch — store the patch array per revision |
| Collaborative real-time editing (like Google Docs) | Operational Transformation or CRDT — beyond JSON Patch |
Compare two JSON documents visually
Paste two JSON objects to see a side-by-side diff with added, removed, and changed fields highlighted.
Open JSON Diff →