14 May 2025 · 5 min read
When a line-based diff tells you nothing
Reformat a JSON file and every line changes. Structural comparison asks a different question.
Diff tools compare lines. That is the right unit for source code, where a line is roughly a statement and moving one is a real change. It is the wrong unit for structured data, where the line breaks are decoration.
The failure
Two JSON documents with identical content, one formatted with two-space indentation and one minified, produce a diff in which every line is different. The tool is not wrong — every line genuinely differs. It is answering a question you did not ask.
The same happens when a serialiser reorders object keys. JSON objects are unordered by definition, so a library that emits keys alphabetically produces a document that is semantically identical and textually unrecognisable.
A diff with three hundred changed lines and zero semantic differences is worse than no diff, because it hides the one change that matters.
Structural comparison
Parse both sides, then walk the resulting values and report paths where they differ. Formatting disappears because it never survives parsing. Key order disappears because objects are compared by key, not position.
$.version changed "1.3.0" → "1.4.0" $.tools[2] added "jwt" $.offline changed false → true $.pwa added true
Four lines instead of three hundred, and each one is a fact about the data rather than about the file.
Where arrays complicate things
Arrays are ordered, so a structural diff must decide whether an element was changed or whether one was inserted and everything shifted. Comparing by index reports the second case as a change to every subsequent element. Comparing by identity needs to know which field identifies an element, which the data does not tell you.
There is no universally right answer. Index comparison is predictable and occasionally noisy; that is the trade DevToolery makes, and it is why array reordering shows up as changes.
Choosing the granularity
- Source code — line diff. Lines are meaningful units.
- Prose — word diff. A reflowed paragraph changes every line and almost no words.
- JSON, YAML, config — structural diff, with a line diff available for reviewing the actual file change.
- Generated files — often neither. Diff the input instead.
A note on review
This is why committing a formatting change and a behavioural change together makes review impossible. The reviewer sees a wall of noise and approves it, which is exactly when the real change slips past. Separate the commits — the tooling cannot rescue you from mixing them.
Tools mentioned