Why a structural diff, not a text diff
Text diff tools compare files line by line, so semantically identical JSON that is formatted differently — minified on one side, pretty-printed on the other — shows up as dozens of changes. A structural diff parses both sides first, then compares the data trees. Whitespace and key ordering are ignored; only real differences remain.
// These two are identical JSON — a text diff reports every line as changed
{"a":1,"b":2}
{
"b": 2,
"a": 1
}
// A structural JSON diff: no differencesReading the diff: added, removed, changed
A JSON diff uses three markers: + for an added key, - for a removed key, and ~ (or a highlighted cell) for a value that changed. Keys that match on both sides are left unmarked.
// Before
{ "name": "Alice", "role": "viewer", "email": "alice@example.com" }
// After
{ "name": "Alice", "role": "admin", "team": "engineering" }
// Diff
"name": "Alice" // unchanged
~ "role": "viewer" → "admin" // changed
- "email": "alice@example.com" // removed
+ "team": "engineering" // addedNested object diff reports the full path
The diff recurses into nested objects and shows the dotted path to each changed value, so you know exactly where in the tree a difference lives without expanding everything.
// Before
{ "config": { "timeout": 30, "retries": 3 } }
// After
{ "config": { "timeout": 60, "retries": 3, "backoff": "exponential" } }
// Diff
~ config.timeout: 30 → 60
+ config.backoff: "exponential"Arrays compare by position — and why order matters
Arrays are diffed by index, not by identity. Element [0] is compared with element [0]. Insert one item at the front and every later element shifts, so each index reads as changed even though the same values are present.
// Before
{ "ids": [10, 20, 30] }
// After — 5 inserted at index 0
{ "ids": [5, 10, 20, 30] }
// Position-based diff
~ ids[0]: 10 → 5
~ ids[1]: 20 → 10
~ ids[2]: 30 → 20
+ ids[3]: 30This is correct for ordered data. For order-independent comparison, sort both arrays by a stable key before diffing. After sorting by id, the two payloads below diff cleanly even though the raw element order differs:
// Before // After
[ {"id": 2}, {"id": 1} ] [ {"id": 1}, {"id": 2} ]
// Sort both by id, then diff → no differencesDiff JSON in code
When you need a diff inside a script, a CI check, or a pipeline instead of the browser tool:
# git diff --no-index — JSON-aware text diff after formatting both files
jq -S . a.json > a.norm.json && jq -S . b.json > b.norm.json
git diff --no-index a.norm.json b.norm.json # -S sorts keys so order/format noise is gone// Node.js — structural patch with deep-diff / json-diff
const { diff } = require('deep-diff') // npm i deep-diff
console.log(diff(before, after)) // array of { kind, path, lhs, rhs }
// or the json-diff CLI, which exits non-zero when files differ:
// npx json-diff a.json b.json# Python — deepdiff returns a dictionary of changes
from deepdiff import DeepDiff # pip install deepdiff
import json
a, b = json.load(open("a.json")), json.load(open("b.json"))
print(DeepDiff(a, b)) # {'values_changed': {...}, 'dictionary_item_added': [...]}Common JSON diff mistakes
- Diffing minified vs pretty-printed JSON with a text tool — use a structural diff to drop the noise.
- Expecting arrays to match by identity — they match by position; sort first if order is irrelevant.
- Ignoring type changes —
"30"(string) and30(number) are different values. - Treating a removed key and a key set to
nullas the same — they are distinct in JSON.
For a step-by-step walkthrough with output formats and more examples, see the JSON diff tutorial. To clean up a payload before comparing, run it through the JSON formatter or check it with the JSON validator first.