How to diff two api responses to identify changes
- Step 1Capture the before and after response bodies — Call the endpoint in the old environment (or before the deploy) and the new one. Copy the raw JSON response body from each — from your HTTP client (Postman, Insomnia, curl), browser DevTools Network tab (right-click the request, Copy response), or your API logs.
- Step 2Paste the base response on the left — Paste the old/expected response into the
JSON A (base)textarea. It must be a single valid JSON value — an object, an array, or even a bare scalar. If your client wraps the body in headers or a log envelope, paste only the body. - Step 3Paste the new response on the right — Paste the new/actual response into
JSON B (modified). Both panels are required; leaving one empty returnsPlease provide both JSON A and JSON B. - Step 4Leave or set the array-order checkbox — There is one option: the
Ignore array element ordercheckbox. Note that arrays are always compared by index in the current build, so for ordered collections (a paginated list) the default behaviour is what you want. For genuinely set-like arrays, see the FAQ on normalising order before pasting. - Step 5Click Compare — The tool parses both inputs and renders the diff list plus the
+added / -removed / ~changedstats. If either side is not valid JSON, the parser error message (for exampleUnexpected token } in JSON at position 41) is shown instead. - Step 6Copy the diff and triage breaking changes — Use
Copy JSONto grab the diff entries as a JSON array ([{type,path,from,to}...]). Removed fields and type changes on fields your client reads are the breaking ones; pure additions are usually safe for tolerant clients. Paste the copied list into your changelog or PR.
What each diff entry type means for an API consumer
The tool emits exactly three entry types. This maps each to the API-compatibility consequence. Colours are how they render in the result list.
| Entry type | Colour | Emitted when | Consumer impact |
|---|---|---|---|
added | Green | A key (or array index) exists in B but not in A | Usually additive / non-breaking — clients that ignore unknown fields are unaffected; strict-schema clients may reject it |
removed | Red | A key (or array index) exists in A but not in B | Breaking if any consumer reads that field — the field silently becomes undefined |
changed | Yellow | Same path, different value (compared with JSON.stringify), including type changes | Breaking when the value's type changed ("42" → 42) or an enum/contract value changed; benign for genuine data updates |
How common API response shapes diff
Verified against the tool's recursive walk. Paths use dot notation for object keys and bracket notation for array indices.
| Scenario | A → B | Diff output |
|---|---|---|
| Field renamed | {"userId":1} → {"user_id":1} | removed userId + added user_id (a rename reads as one removal and one addition) |
| Type change | {"id":"42"} → {"id":42} | changed id with - "42" / + 42 |
| Key reordered only | {"a":1,"b":2} → {"b":2,"a":1} | No differences — object key order is ignored |
| Array grew by one item | [1,2] → [1,2,3] | added [2] with + 3 |
| Null vs absent | {"middle":null} → {} | removed middle with - null (null is a present value) |
Cookbook
Real before/after response pairs, anonymised. The left block is JSON A, the right is JSON B, and the diff is what the tool prints. PII values are placeholders.
A typed client breaks on a string-to-number id
ExampleThe backend switched the user id from a string to an integer. A TypeScript client typed id: string now mis-parses it. The tool catches this as a single changed entry — there is no separate 'type mismatch' badge, the from/to values reveal the type shift.
JSON A (v1 response): JSON B (v2 response):
{ {
"id": "4815", "id": 4815,
"name": "Acme Co" "name": "Acme Co"
} }
Diff:
~1 changed
changed id
- "4815"
+ 4815A removed field that a consumer depended on
Examplev2 dropped the legacyToken field. Pure additions are safe; this removal is the breaking one because a mobile client still reads it.
JSON A (v1): JSON B (v2):
{ {
"sessionId": "s_91", "sessionId": "s_91",
"legacyToken": "tok_abc", "refreshToken": "tok_xyz"
"refreshToken": "tok_xyz" }
}
Diff:
-1 removed +0 added
removed legacyToken
- "tok_abc"Confirming a serializer change is cosmetic
ExampleA framework upgrade changed JSON key order. The raw text diff looks scary; the semantic diff is empty, proving the contract is unchanged.
JSON A (Jackson v2.15): JSON B (Jackson v2.17):
{ {
"id": 7, "name": "Beta",
"name": "Beta", "active": true,
"active": true "id": 7
} }
Diff:
No differences
→ The two JSON values are identical.Diff a single item, not the whole page
ExampleComparing two full paginated lists is noisy because index-by-index array comparison flags every shifted element. Diff one representative object instead to get the structural change cleanly.
Instead of pasting the full `data` arrays, paste item 0: JSON A: oldResponse.data[0] JSON B: newResponse.data[0] Diff: +1 added added loyaltyTier + "gold" → One structural change per item, no positional array noise.
Enum value tightened in an error envelope
ExampleThe error code enum changed from a free-text string to a coded slug. Existing clients matching on the old string will fall through their switch statements.
JSON A: JSON B:
{ {
"error": { "error": {
"code": "Not Found", "code": "NOT_FOUND",
"status": 404 "status": 404
} }
} }
Diff:
~1 changed
changed error.code
- "Not Found"
+ "NOT_FOUND"Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
A type change is reported as `changed`, not a typed mismatch
By designThe tool detects a difference when JSON.stringify(a) !== JSON.stringify(b), so "42" vs 42 does show up — as a changed entry whose from/to reveal the type shift. There is no dedicated 'type mismatch' colour or label; read the values to confirm a string became a number. This is the most reliable signal of a silently-breaking API change.
Reordered array elements show as many `changed` entries
Positional by designArrays are compared strictly by index. ["a","b"] vs ["b","a"] produces changed [0] and changed [1], not a 'moved' notation. The Ignore array element order checkbox does not currently alter this behaviour in the diff engine, so for set-like arrays sort both sides before pasting (see the FAQ).
One side is not valid JSON
Parse errorBoth inputs go through JSON.parse. A trailing comma, a single-quoted key, an unquoted key, or a wrapping log envelope throws, and the raw parser message (for example Unexpected token , in JSON) is shown. Strip non-JSON wrappers first, or run the body through the prettifier/format-fixer at /tool/json-format-fixer to repair near-JSON before diffing.
Only one panel filled in
InvalidBoth JSON A and JSON B are required. Leaving either empty (or whitespace-only) returns Please provide both JSON A and JSON B. before any parsing happens.
`1` vs `1.0` shows no difference
ExpectedJavaScript's JSON.parse reads both 1 and 1.0 as the same number, so a numeric-format-only change is invisible here. If you specifically need to detect integer-vs-decimal formatting (rare, usually a serializer concern), this tool cannot — the distinction is lost at parse time.
`null` vs an absent key
Removed{"x":null} diffed against {} reports removed x with value null, because null is a present value and an absent key is not. If your API uses null and 'field omitted' interchangeably, treat removed/added of a null value as semantically equivalent during review.
Free-plan input over 2 MB per side
Upgrade requiredOn the free plan each pasted value is capped at 2 MB. A larger response returns Free plan supports JSON inputs up to 2 MB. Upgrade to Pro for unlimited input size. Diff a single representative item rather than a full paginated dump to stay under the cap, or upgrade to Pro.
An object turns into an array at the same path
ChangedIf a field was {} in A and [] in B (or vice-versa), the tool does not descend into both — it records a single changed entry at that path with the whole old and new value. Container-shape changes like this are almost always breaking for clients that indexed into the field.
Identical inputs
SupportedWhen the two values match (after key-order normalisation), the result panel shows The two JSON values are identical. This is the positive confirmation you want when validating that a deploy left a contract untouched.
Frequently asked questions
How do I tell if a change is breaking?
Map the diff types to risk: removed on a field a consumer reads is breaking; changed is breaking when the value's type changed ("42" → 42) or a contract enum value changed; added is usually safe for tolerant clients that ignore unknown fields, but breaks strict-schema validators. Scan the removed and changed lines first.
Does the tool flag type changes specially?
Not with a separate label. A "42" to 42 change is a single changed entry — you read the - "42" / + 42 pair to see that a string became a number. There is no 'type mismatch' badge; the value pair is the evidence.
Why does reordering an array show so many changes?
Arrays are compared by index, not by value-set. Position 0 in A versus position 0 in B, and so on. A reorder therefore reads as a series of changed entries. To compare order-independent arrays, sort both arrays consistently before pasting — for example flatten and sort with /tool/json-flattener on a stable key, then diff.
What does the `Ignore array element order` checkbox do?
It is a UI option carried in the tool's options object, but in the current build the diff engine compares arrays positionally regardless of its state, so toggling it does not change the output. For true order-insensitive comparison, normalise (sort) both arrays before pasting.
How do I diff large paginated responses efficiently?
Don't paste the full pages — index-by-index array comparison makes them noisy and you may hit the 2 MB free cap. Diff one representative element: paste oldResponse.data[0] on the left and newResponse.data[0] on the right. You get the structural and value diff for one object without positional array noise.
Can I upload a file or fetch a URL?
No. This tool is paste-only: two textareas, JSON A (base) and JSON B (modified). Copy each response body from your HTTP client or DevTools and paste it. There is no file picker and no network fetch — that is also why nothing leaves your browser.
Does object key order matter?
No. Objects are compared by key membership and value, so {"a":1,"b":2} and {"b":2,"a":1} diff clean. This is exactly what you want when a framework or serializer upgrade reorders keys but keeps the contract identical.
How are nested objects handled?
Recursively. The walk descends into nested objects and arrays, building paths like data.user.address.city or orders[3].total. Each leaf difference is reported at its full path, so you see precisely where in the structure the change lives.
What happens with a bare scalar or array at the root?
Both are supported. A root-level scalar change (1 vs 2) is reported at path (root). A root-level array is walked by index like any nested array. You are not required to paste an object at the top level.
Can I get the diff as machine-readable output?
Yes. Copy JSON puts the diff on your clipboard as a JSON array of entries — [{"type":"changed","path":"id","from":"42","to":42}, ...]. Paste that into a changelog, a PR comment, or a script that gates a release on the presence of removed/type-changed entries.
Is the API response data transmitted to JAD Apps?
No. Both inputs are parsed and diffed in your browser with JSON.parse. Response bodies — including auth tokens, customer PII, and internal fields — are never sent to a server. You can confirm this in DevTools: clicking Compare makes no network request.
I only want to diff one part of a huge response. How?
Extract the sub-tree first with the JSON Path Extractor at /tool/json-path-extractor — pull out $.data.user from both responses, then paste the two extracted sub-trees here. That keeps the diff focused and well under the free size cap.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.