Compare two JSON files by structure, not by text
A text diff of two JSON files reports the formatting and leaves you to find the meaning. A structural diff compares values at the same path, so what you see is what actually changed.
Why a text diff is the wrong tool
JSON has no canonical form. The same data can be written with two-space or four-space indent, with the keys in any order, with 1.0 where another producer wrote 1, and with a trailing newline or without one. None of that is a change to the data, and all of it is a change to the text. Run diff over two exports of the same record from two services and the output is mostly noise.
The opposite failure is quieter and worse. A real change — one property renamed deep inside the twelfth element of an array — is a handful of lines in a file of a hundred thousand, sitting beside a hundred lines of reformatting. It is there in the diff, and nobody sees it.
What a structural comparison does instead
- Object members are matched by key, so key order never registers as a change.
- Numbers are compared by value:
1.0,1and1e0are the same number. - Whitespace between tokens is invisible, because the comparison runs over the parsed index rather than the text.
- Array elements are aligned before they are compared, so inserting one element does not mark every element after it as different.
- Every difference is reported at a JSON Pointer, which is the address you can use in code, in a bug report, or in a patch.
Aligning arrays, which is the hard part
An array is the one place where "the same element" needs deciding. JSONParse offers three answers and lets you pick, because the right one depends on what the array means.
| Mode | How elements are paired | Use it when |
|---|---|---|
| Smart alignment | By an identity key (id, uuid, …) when every object in the array has one and the values are unique; otherwise by content, using a longest-common-subsequence alignment over structural fingerprints. | The default. Lists of records, config blocks, anything where an element has an identity or where inserts and deletions happen. |
| By position | Element 0 against element 0, element 1 against element 1. | Fixed-shape arrays such as coordinate pairs or matrix rows, where position *is* the meaning. |
| Ignore order | As a multiset: equal elements pair up wherever they sit, and only genuine additions and removals are reported. | Sets written as arrays — tags, permissions, feature lists — where order is an accident of serialisation. |
With smart alignment, an element that only moved is reported as moved rather than as a removal and an unrelated addition. Turn that off if you want position changes ignored entirely.
A worked example
Two deployment manifests, one release apart:
{ "replicas": 3, "regions": ["eu-west-1", "us-east-1"], "retries": "3" }
{ "replicas": 5, "regions": ["ap-south-1", "eu-west-1", "us-east-1"], "retries": 3 }
A text diff calls both lines changed and leaves the reading to you. The structural comparison reports three things, each at its own address:
~ /replicas 3 → 5
+ /regions/0 "ap-south-1"
~ /retries "3" → 3
The third line is the one that matters: retries went from a string to a number. That is a type change, it is the kind of thing that breaks a consumer at three in the morning, and in a text diff it looks exactly like the harmless ones.
Exporting the result
A comparison is worth more when it leaves the screen. Two exports are offered: an RFC 6902 JSON Patch, which is a list of operations that transform the first document into the second and can be applied by any patch library, and a change report, which is one line per difference with its pointer, suitable for a pull request comment or a ticket.
The patch is ordered so that it actually applies: within an array, removals are emitted by their original index in descending order, then insertions by their new index, then replacements. Moved elements are emitted as a removal and an insertion rather than as a move, because a move whose source index has already shifted is the classic way these patches go quietly wrong.
Size, and where it runs
Both documents are parsed into the same flat typed-array index the viewer uses, then merged into a single diff tree that the same virtualised renderer draws. Nothing is uploaded, and the comparison runs in a Web Worker, so the window stays responsive while two large files are aligned.
| Limit | Value | What happens at the edge |
|---|---|---|
| Merged rows | 2,000,000 | The comparison stops and says it is incomplete rather than exhausting memory. |
| Document size | Bounded by available memory | Both files are held at once, so budget for roughly twice what the viewer needs for one. |
| HTTP API body | 4 MB total | Both documents together. Larger pairs belong in the browser, where they are never transmitted. |
From a script or an agent
The same comparison is available at POST /api/diff and as the json_diff tool on the MCP server, so a CI job can fail a build on an unexpected schema change and an assistant can answer "what changed between these two payloads" without diffing text. See the API reference for the request shape.
curl -sS https://jsonparse.online/api/diff \
-H 'content-type: application/json' \
-d '{
"left": "{\"replicas\":3}",
"right": "{\"replicas\":5}",
"options": { "output": "patch" }
}'
Frequently asked questions
How is this different from running diff on two JSON files?
A text diff compares lines of characters, so reindenting a file or reordering its keys shows up as a change and a genuine change can be buried in that noise. This compares the parsed documents: values are matched by key and by path, numbers are compared by value, and formatting is invisible.
Does the order of keys in an object count as a change?
No. Object members are matched by name, so two documents whose keys appear in different orders compare as identical. JSON objects are unordered by definition and every parser is free to present them in any order.
What happens when an element is inserted into the middle of an array?
With the default smart alignment, the inserted element is reported as an addition and everything after it stays matched to its counterpart. Switch to positional comparison if you want index-for-index behaviour instead, which will mark the remainder of the array as changed.
Can I ignore case, whitespace or tiny numeric differences?
Yes. String values can be compared case-insensitively, with leading and trailing whitespace trimmed, or with repeated whitespace collapsed, and numbers can be given an absolute tolerance so that values within it compare as equal. Keys are always compared case-sensitively.
Are my files uploaded when I compare them?
No. Both files are read with the browser File API and compared in a Web Worker on your own machine, the same as the viewer. The HTTP API and the MCP server are the exception and receive whatever you send them, which is why the documents there are capped at 4 MB.
Can I apply the result to the first file?
Yes. Export the comparison as an RFC 6902 JSON Patch and apply it with any standard patch library; the operations are ordered so that applying them to the original document reproduces the changed one exactly.