How to compare two JSON API responses
You have two responses from the same endpoint — from staging and production, or from before and after a deploy — and you want to know what really changed. A plain text diff is usually the first thing people try, and usually the wrong tool.
Why text diffs mislead
Here are two responses that mean almost the same thing.
A (before)
{
"orderId": "A-1001",
"status": "paid",
"updatedAt": "2026-10-20T09:30:00Z",
"items": [
{ "sku": "BK-001", "qty": 2, "price": 12.5 },
{ "sku": "PN-114", "qty": 1, "price": 3.99 }
]
}
B (after)
{
"status": "paid",
"orderId": "A-1001",
"items": [
{ "sku": "PN-114", "qty": 1, "price": "3.99" },
{ "sku": "BK-001", "qty": 3, "price": 12.5 }
],
"updatedAt": "2026-10-21T14:02:11Z"
}
A line-by-line diff marks nearly every line as changed: the keys moved, the array items swapped places, and the timestamp is different. Hidden in that noise are the two changes that matter:
BK-001’s quantity went from2to3.PN-114’s price changed type, from the number3.99to the string"3.99". That kind of change breaks clients while every value still looks the same.
Compare structure, not text
A structural JSON diff parses both documents and compares values, so two kinds of noise disappear:
- Key order.
{"a":1,"b":2}and{"b":2,"a":1}are the same object. - Formatting. Whitespace and indentation don’t matter.
Arrays need more thought, because sometimes their order matters and sometimes it doesn’t.
Decide how arrays should be matched
There’s no single right answer, so choose for each comparison:
| The array holds | Compare by | Why |
|---|---|---|
Records with an id (id, sku, uuid…) |
a matching key | An item that moved isn’t a change; an item whose fields changed is |
| Ordered steps, rankings, pages of results | position | The order is the meaning, so a swap is a real change |
| Sets, like tags or permissions | ignoring order | Only membership matters |
In the example, matching items by sku gives exactly the two real changes. Comparing them by position reports six
changes, because every field of both items appears to differ.
Be careful with ignoring order. It’s right for sets and wrong for anything where order carries meaning, so choose it per array, not for the whole document.
Ignore fields that always change
Timestamps, request ids, trace ids and ETags differ on every call. Exclude them by name (updatedAt) or by
pattern (*At, trace*) so they don’t hide the real changes.
Watch for type changes
Value changes are easy to spot. Type changes aren’t: 3.99 → "3.99", 1 → true, [] → null. A good diff
reports them separately, because they’re often contract breaks rather than data changes. If you have a schema for
the response, a JSON Schema validator catches them before the diff does.
Doing it in CurlyJSON
CurlyJSON’s JSON Diff works this way:
- Key order and formatting never count as changes.
- Smart array matching, the default, looks for an id field such as
id,uuidorskuand matches items by it. Without one, it lines up unchanged items so that one insertion is one change. You can also match by a key you choose, compare by position, or ignore order. - Ignore fields takes names and wildcards, and suggests timestamp-like fields when they change.
- Changes are labelled Added, Removed, Changed or Type changed.
- Big numbers stay exact, so two different 64-bit IDs never compare as equal. Plain
JSON.parsecan’t promise that: JavaScript can silently round large JSON integers. - You can export a Markdown table for a ticket, or an RFC 6902 JSON Patch.
It runs in your browser, so neither response is uploaded. Paste A and B from this article to see the changes for yourself; the JSON Diff docs cover every option.