JSON Diff
See exactly what changed between two documents.
A text diff of two JSON files is noisy: reorder a few keys or reformat the file and every line looks changed. Jsonviewpro compares the parsed structures instead, so you get a list of real differences — each with the JSON path you can use to find it.
Before
After
Why a text diff is the wrong tool here
Git compares lines. JSON does not care about lines, so the two disagree constantly. Reformat a file and every line changes while the data is identical. Reorder two keys and a text diff reports four changed lines for a document that means exactly the same thing. Add one element to the front of an array and every element after it appears to have changed.
A structural diff parses both sides first and compares the resulting values. Formatting, indentation and key order stop existing before the comparison starts.
{ "name": "api", "port": 8080, "tags": ["a", "b"] }
{ "port": 8080, "tags": ["a", "b"], "name": "api" }Jsonviewpro reports 0 changes for that pair. Key order in a JSON object carries no meaning, so a tool that reports it as a difference is reporting noise.
The four kinds of change
Type changed is split out from changed deliberately, because it is the one that breaks consumers. A number that became a string still looks right in a rendered UI and still fails value.toFixed(2). When a diff against yesterday's response shows a type change, you have found the cause of the bug rather than a symptom of it.
type-changed $.limits.seats number → stringHow arrays are aligned
Arrays are the hard part. [a, b, c] compared to [b, c, d] has no single correct answer — it depends on whether the array is an ordered list or a set of records that happen to be in an order. Jsonviewpro picks one of three strategies, in this order.
1. Match on an identifier
If every item on both sides is an object carrying the same identifying field — id, _id, uuid, key, slug, code, sku or name — and those values are unique and non-null, items are matched by identity rather than by position. Reordering then produces no changes at all.
before [ {id:1, role:"admin"}, {id:2, role:"viewer"}, {id:3, role:"viewer"} ]
after [ {id:3, role:"viewer"}, {id:1, role:"owner"}, {id:4, role:"viewer"} ]
removed $.users[1] {"id":2,"role":"viewer"} array item (id: 2)
changed $.users[1].role "admin" → "owner"
added $.users[2] {"id":4,"role":"viewer"} array item (id: 4)Three changes, not six. User 3 moved from the end to the front and is correctly reported as unchanged — a positional diff would have called every row different.
2. Longest common subsequence
Without a usable identifier, arrays of up to 400 items on each side are aligned by longest common subsequence — the same technique a text diff uses on lines, applied to parsed values. Inserting an element reports one insertion rather than shifting everything after it.
before [1, 2, 3]
after [1, 9, 2, 3]
added $.values[1] 93. Position, for very large arrays
Above 400 items the LCS table would grow with the product of both lengths, so comparison falls back to index alignment: item 0 against item 0, and any surplus reported as added or removed. It is less clever, and it keeps memory predictable on a 50,000-row export. If you are diffing arrays that large, giving the records a stable id gets you strategy 1 and a far better result.
Reading the paths
Every change carries a JSON path you can use directly in the viewer or a JSONPath query. One detail is worth knowing: for matched and added items the index is the position in the new document, and for removed items it is the position in the old one. That is what you want when the next step is editing one side or the other, but it means two changes can legitimately quote the same index, as $.users[1] does above.
Changes are sorted by path with numeric awareness, so [2] comes before [10] and related fields sit together rather than being scattered by change type.
When the diff is empty but the files differ
An empty result means the parsed values are equal, and that is a stronger statement than the files being equal. Whitespace, key order, 1.0 versus 1, and \u0041 versus A all vanish at parse time. If you need byte equality — for a signature or a checksum — a structural diff is the wrong check; compare the raw text instead.
At the other extreme, comparison stops after 20,000 individual changes and says so. Two documents that different are usually not two versions of the same thing, and a list that long is not something anyone reads — compare a narrower branch instead.
Where this gets used
- API regression checks — capture a response before and after a deploy and see what actually moved, rather than eyeballing two 3,000-line files.
- Config drift — compare staging and production configuration to find the one flag that differs.
- Snapshot test failures — when a snapshot assertion fails, the structural diff usually identifies the real change in seconds.
- Vendor upgrades — compare a response from the old version of an integration against the new one before you trust the changelog.
Frequently asked questions
Does key order affect the diff?
Why is a whole array item reported as added and removed instead of changed?
id, uuid, sku or name, matching happens by identity and you get precise field-level changes instead.Can I compare two files instead of pasting?
What is the difference between "changed" and "type changed"?
5 to "5", or an object to null. The second is the one that breaks code, so it is counted separately.How large can the documents be?
Are both documents kept private?
Related JSON tools
- JSON ViewerExplore JSON as a collapsible tree with search and copyable paths.
- JSON FormatterPretty-print messy JSON with your choice of indentation.
- JSON AnalyzerProfile structure and surface data-quality problems.
- JSON ValidatorCheck syntax with precise line, column and cause.
- JSON Schema GeneratorInfer a JSON Schema from a sample document.