Blog
Comparing JSON Isn't the Same as Comparing Text
Why a line-based diff misreads JSON, and what it means to compare two documents structurally instead of textually.
Take a small config file, reorder two of its top-level keys, save it, and run it through an ordinary text diff against the original. The result looks alarming: nearly every line shows as removed and re-added, even though nothing about the data actually changed. A human glancing at that diff would assume a large edit happened. Nothing did — the object's keys just moved around, and JSON, unlike a list of instructions or a CSV, doesn't assign any meaning to the order they appear in. That mismatch between what changed textually and what changed semantically is the core reason JSON needs its own comparison logic instead of borrowing the line-based diff built for source code and prose.
What a text diff is actually measuring
Tools like diff, and the diff view in most version control systems, work on lines of text. They find the longest common subsequence of lines between two files and report everything outside that subsequence as an insertion or deletion. That model is a great fit for source code and prose, where line order is inherently meaningful — moving a function definition above another one is a real structural change, and a diff should say so. But JSON is a serialization of a tree: objects with unordered key-value pairs, arrays with ordered elements, and scalar values at the leaves. Pretty-printing that tree onto lines of text is a presentation choice, not a property of the data. Two JSON documents can be byte-for-byte different — different indentation, different key order, different line breaks — while representing the exact same structure, and a line diff has no way to know that, because it was never told the data has any structure to begin with.
Whitespace makes the same point in miniature. Minify a JSON file to a single line and diff it against the pretty-printed version, and a text diff reports the entire file as one giant change, because from a text diff's perspective, a single 900-character line sharing zero "lines" in common with 40 shorter lines really is completely different content. The information content is identical. The formatting is not. A tool that can't separate those two things is answering the wrong question.
What a structural diff compares instead
A JSON-aware diff parses both documents into their actual tree structure first, then walks the two trees in parallel, comparing them node by node rather than line by line. An object's keys get matched by name, not by position — so {"a": 1, "b": 2} and{"b": 2, "a": 1} are recognized as equivalent regardless of which key was written first. Arrays are the more interesting case, because array order generally does carry meaning (a list of steps, a sequence of events), so a well-built structural diff typically treats array reordering as a real change while still matching elements by value where it can, rather than assuming position 0 must always correspond to position 0. The output that comes out the other end isn't a wall of red and green lines; it's a short, specific list: this key's value changed from X to Y, this key was added, this key was removed, this array element moved from index 2 to index 5. Each entry maps to something a human actually did to the data, not an artifact of how it happened to be printed.
Where this actually matters
API responses are the most common place this bites people. Two payloads captured minutes apart from the same endpoint might differ only in a timestamp field and a request-tracing ID, but if the server's JSON serializer doesn't guarantee stable key ordering — and plenty don't — a text diff drowns those two genuine changes in dozens of order-related false positives. Someone debugging "why did this integration test start failing" ends up scanning a huge diff for the one line that matters, when a structural comparison would have shown exactly two entries and nothing else.
Configuration management has the same problem in a different shape. Infrastructure tools that round-trip JSON or YAML-as-JSON through their own serializers routinely re-sort keys alphabetically as a side effect of writing the file back out. A team reviewing a pull request against such a file, using a normal code-review diff, can find themselves reviewing a 100-line change that is, structurally, a single value flip. That's not just noisy — it's genuinely risky, because a reviewer's attention gets spent verifying dozens of no-op reordering lines instead of the one line that actually needs scrutiny, and it's exactly the kind of change where a real edit is easy to miss inside the clutter.
Snapshot testing is a third case worth naming, because it's where the gap causes the most wasted engineering time. A test that serializes an object to JSON and compares it against a saved snapshot will fail on any key-order change even if a library upgrade is the only thing that shifted internal serialization order, forcing someone to manually confirm "yes, this failure is cosmetic" over and over. A structural comparison — the approach behind theJSON diff checker on this site — treats that case correctly from the start: no data changed, so there's nothing to flag, regardless of which order the serializer happened to emit the keys in.
The underlying idea generalizes
None of this is specific to JSON syntax — it's really about recognizing when a format encodes a tree or a set rather than a sequence, and choosing a comparison method that matches. XML, YAML, and even certain database export formats have the same unordered-object property and the same pitfall when diffed as plain text. The general lesson is that a diff tool is only as good as its model of what "the same" means for the data it's looking at — for source code, line order is the data; for JSON, it usually isn't, and treating it as if it were produces a diff that's technically accurate about the bytes and almost useless about the content.
