You changed a serializer, bumped a dependency, or moved an endpoint to a new service. Now you want to know one thing: did the response change?
The usual move is to save both responses and run diff before.json after.json. That works until it doesn't.
Why text diffs lie about JSON
Take this response before and after a refactor:
{ "id": 42, "name": "Alice", "role": "admin",
"tags": ["beta", "staff"],
"plan": { "tier": "pro", "seats": 5 } }
{ "id": 42, "name": "Alice", "roles": ["admin", "editor"],
"tags": ["staff", "beta"],
"plan": { "tier": "pro", "seats": "5" } }
A line diff has three problems here:
- Formatting noise. If one side is minified or the keys come out in a different order, every line "changed".
-
Reordered arrays look like edits.
["beta", "staff"]vs["staff", "beta"]is usually the same set of tags, but it shows up as two changed lines. -
Type changes are nearly invisible.
5vs"5"is two characters. In a 400-line response you will scroll right past it, and it's the one that breaks clients.
Diff the structure, not the text
A structural diff parses both sides and compares values by path. Running the two responses above through one gives:
| Path | Before | After | Change |
|---|---|---|---|
$.role |
"admin" |
(missing) | removed |
$.roles |
(missing) | ["admin","editor"] |
added |
$.tags[0] |
"beta" |
"staff" |
changed |
$.tags[1] |
"staff" |
"beta" |
changed |
$.plan.seats |
5 |
"5" |
changed |
Whitespace and key order are gone from the picture. But the tags rows are still noise.
Turn on order-insensitive arrays
Compare arrays as sets instead of by index and the same diff becomes:
| Path | Before | After | Change |
|---|---|---|---|
$.role |
"admin" |
(missing) | removed |
$.roles |
(missing) | ["admin","editor"] |
added |
$.plan.seats |
5 |
"5" |
changed |
Three rows, and every one is real:
-
rolewas renamed torolesand became an array. Breaking change for every client readingrole. -
seatswent from number to string. Breaking change for anything doing math on it.
For arrays of objects (say, a list of line items), matching elements by their id instead of by position gives the same benefit: inserting one item at the top doesn't mark every following item as changed.
Doing it in the browser
I built this into Formaty's JSON Diff: paste the old response on the left, the new one on the right, and toggle order-insensitive comparison (arrays of objects are then matched by id). It shows side-by-side or inline highlighting plus the path-level change list. Everything runs locally, so production payloads never leave your machine.
If you'd rather script it, the same idea works in a test: parse both sides, walk them by path, and treat arrays as multisets unless order matters for that field.
FAQ
Does key order matter? No. Objects are compared by key, so {"a":1,"b":2} equals {"b":2,"a":1}.
Can I export the result? Yes. Copy or download the change list as a JSON report to attach to a PR or ticket.
Formaty is free and open source (GitHub). 60-second demo.
Top comments (0)