JRJI
All articles
Tooling · 5 min

JSON Diff, explained: structural vs textual

Why line-by-line diffing lies to you, and how a structural delta gives you the truth about what changed.

You have two versions of an API response and one question: what actually changed? Reach for git diff or any text comparison tool and you will get an answer — just not necessarily a true one.

Where textual diff falls apart

A line-based diff compares characters, not meaning. For JSON, that distinction matters constantly:

Key order. {"a":1,"b":2} and {"b":2,"a":1} are the same object per the JSON spec, but a text diff flags every line as changed. Many serializers (Go maps, Python dicts pre-3.7, most databases) make no ordering guarantees, so identical data routinely produces wall-of-red diffs.

Formatting noise. Re-indent a document, switch from tabs to spaces, or minify it, and a text diff reports a 100% rewrite of a file whose data is untouched.

Cascading misalignment. Insert one element at the top of a long array and a line diff often marks every subsequent line as modified, burying the single real change in pages of noise.

The result: you either eyeball two documents side by side, or you trust a diff that overstates changes by an order of magnitude.

What a structural diff does instead

A structural diff parses both documents first, then compares the resulting values:

  • Objects are compared key by key, regardless of order or formatting.
  • Arrays are compared element-wise, with insertions and removals detected as such.
  • The output is a delta: a compact description of exactly which paths were added, removed, or changed, and what the before/after values are.

JRJI’s JSON Diff uses jsondiffpatch, whose delta format is worth learning to read:

{
  "status": ["draft", "live"],
  "count": [2, 3],
  "tag": ["v2"]
}

The convention: a two-element array [old, new] means changed, a one-element array [new] means added, and [old, 0, 0] means removed. Nested objects diff recursively, so a change five levels deep shows up at its exact path — not as “these 40 lines differ”.

When to use which

Structural diffing is not always the right tool. A quick guide:

SituationUse
Comparing API responses between environmentsStructural
Reviewing a config change in a PRTextual (you care about the file as written)
Debugging “same data, different serializer”Structural
Auditing whitespace/formatting changesTextual
Verifying a migration preserved dataStructural

The rule of thumb: if you care about the data, diff the structure. If you care about the file, diff the text.

Try it on your own payloads

Paste two versions of any JSON document into the JSON Diff tool — both stay in your browser, nothing is uploaded — and compare the delta against what your text diff claimed. The gap between the two is exactly the noise you have been reading around.