Skip to main content

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

Size: 0 BLines: 0

After

Size: 0 BLines: 0

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.

Identical to a structural diff, four changed lines to a text diff
{ "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

TypeMeansExample
AddedThe path exists on the right and not on the left.A new field, or a new array item.
RemovedThe path exists on the left and not on the right.A deleted field.
ChangedSame path, same type, different value."admin" → "owner"
Type changedSame path, different JSON type.5 → "5"

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.

Reported as a type change, not a value change
type-changed   $.limits.seats   number → string

How 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.

Three users, reordered, one edited, one swapped out
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]   9

3. 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?
No. Both sides are parsed before anything is compared, and object key order carries no meaning in JSON. Reordering every key in a document produces zero changes.
Why is a whole array item reported as added and removed instead of changed?
Because the items could not be matched to each other. If each item has a unique 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?
Yes — drop a file onto either editor, or paste into one and load a file into the other. Both are read locally.
What is the difference between "changed" and "type changed"?
Changed means the same JSON type with a different value. Type changed means the type itself moved — 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?
Comparison is capped at 20,000 reported changes, and arrays over 400 items are aligned by position rather than by longest common subsequence to keep memory bounded. Documents well into the megabytes compare fine.
Are both documents kept private?
Yes. Both are parsed and compared in your browser; neither is sent anywhere.