Compare JSON

Compare JSON by value instead of by text. Reordered keys are not changes, reordered array items are not changes, and a number that quietly became a string is — which is the one every other comparison misses.

Free · No signup · Nothing leaves your browser

Compares two JSON documents by value rather than by text. Reordered keys are not changes, and neither are reordered array items — so what is left on the page is the difference you were looking for, not the fifty the ordering caused.

2 differences and 3 moves

  • type
    $.page

    1"1"

    Same text, different type — number became string. These render identically, so a diff that compares displayed values shows nothing here.

  • moved
    $.users[id=42]

    { 3 keys }{ 3 keys }

    Position 0 → 1. The item itself may also have changed; those are listed separately.

  • changed
    $.users[id=42].role

    "admin""owner"

  • moved
    $.users[id=17]

    { 3 keys }{ 3 keys }

    Position 1 → 2. The item itself may also have changed; those are listed separately.

  • moved
    $.users[id=88]

    { 3 keys }{ 3 keys }

    Position 2 → 0. The item itself may also have changed; those are listed separately.

Changed11 changed type
Added0present only in the second
Removed0present only in the first
Moved3same item, new position

Everything runs in your browser — neither document is uploaded, logged or stored. Comparing two API responses usually means comparing two sets of real customer records, which is a good reason not to send them to someone else's server.

Tekk is a spec-driven development platform for people building software with AI coding agents — for catching the difference between what was specified and what got built.

Nine differences, or one

Two versions of the same API response. The records came back in a different order, one role changed, and page arrived as a string this time.

Matched by position, which is what most comparison tools do:

~ $.page              1 → "1"
~ $.users[0].id       42 → 88
~ $.users[0].email    "ada@example.com" → "alan@example.com"
~ $.users[0].role     "admin" → "viewer"
~ $.users[1].id       17 → 42
~ $.users[1].email    "grace@example.com" → "ada@example.com"
~ $.users[1].role     "editor" → "owner"
~ $.users[2].id       88 → 17
~ $.users[2].email    "alan@example.com" → "grace@example.com"
~ $.users[2].role     "viewer" → "editor"

Ten lines, nine of them noise, and the real change buried in the middle of them.

Matched by identity:

~ $.page                    1 → "1"      (number became string)
↔ $.users[id=42]            position 0 → 1
~ $.users[id=42].role       "admin" → "owner"
↔ $.users[id=17]            position 1 → 2
↔ $.users[id=88]            position 2 → 0

Same two documents. One change, one type change, three moves.

What counts as a difference

Not a difference:

  • Key order. JSON objects are unordered — RFC 8259 §4 says so directly. {"a":1,"b":2} and {"b":2,"a":1} are the same object, and any tool that flags it is diffing the file rather than the data.
  • Whitespace and indentation. Not part of the value.
  • How a number was written. 1, 1.0 and 1e0 are one number.
  • Array order, when items can be matched by identity — reported as a move, not a rewrite.

A difference, including the ones that are easy to miss:

  • A type change with no visible change. 42"42" looks like nothing happened. It breaks every client doing ===, and it is what an ORM upgrade or a new API gateway does to your integer IDs without mentioning it.
  • null versus absent. A key present and null is not the same as a key that is gone. Most consumers treat them differently and most diffs do not.
  • false versus 0 versus "". All falsy, none equal.
  • Two large integers that a double cannot tell apart. 12345678901234567890 and ...891 are the same value after JSON.parse. Compared here as written, they are not.

Why identity matching is off by default anywhere else

Because it needs a decision, and most tools would rather not make one. Matching on a key means picking the key.

This one picks from id, _id, uuid, key, slug, sku, email, name — in that order — and only uses one if every item on both sides carries it and the values are unique on both sides. A repeated id would make the matching arbitrary, so it declines and says why.

When there is no such key but the two arrays hold exactly the same values in a different order, that is still a reorder and is reported as one. Only when neither holds does it fall back to comparing by index, and then it tells you it did — because a positional comparison of a reordered array is the result that sends people hunting for a bug that is not there.

Where a side-by-side view loses

A side-by-side pane is the right shape when you are reviewing a change you made. It is the wrong shape when you are hunting a difference you did not make, because the work is scrolling two panes looking for colour.

Six paths is the answer. $.users[id=42].role is something you can search for in your editor, paste into a ticket, or hand to whoever owns that endpoint.

How it works

  1. 1

    Paste both documents

    It compares as you type. Whitespace, indentation and key order are ignored, because none of them are part of the value — an object with its keys in a different order is the same object, and a json diff that says otherwise is reporting on the formatting.

  2. 2

    Leave array matching on identity

    When every item carries an id, uuid, email or slug, items are matched by it rather than by index. Two API responses holding the same records in a different order then produce moves instead of a rewrite of every field, which is the difference between a readable result and an unreadable one.

  3. 3

    Read the paths

    Every difference is reported as a path you can act on — $.users[id=42].role rather than "line 14". Copy the whole set as text for a ticket, or switch matching back to position when index order is itself the thing you are checking.

Frequently asked questions

Do you store or send my data anywhere?
No. The comparison is a few kilobytes of JavaScript running on your machine — there is no server call, nothing is logged and there is no account. Comparing two API responses usually means comparing two sets of real customer records, which is a good reason not to hand them to someone else's server.
Why does it say two documents match when the text is different?
Because they are the same value. JSON objects are unordered by definition — RFC 8259 is explicit about it — so {"a":1,"b":2} and {"b":2,"a":1} carry identical information. Whitespace is not part of the value either, and neither is whether someone wrote 1, 1.0 or 1e0. A text diff shows all three as changes. None of them are.
My two API responses have the same records but every field shows as changed.
That is positional matching, and it is what most tools do. If the ten records came back in a different order, item 0 is compared against a different record entirely, so every field inside it differs. Switch array matching to identity and the same data produces a handful of moves and whatever actually changed. This is the single biggest source of noise when you compare JSON.
How does identity matching decide what identifies an item?
It looks for id, _id, uuid, key, slug, sku, email or name — in that order — and only uses one if every item on both sides has it and the values are unique. Matching on a key that repeats would be a guess presented as a result, so it does not. Failing that, if the two arrays hold exactly the same values in a different order, it reports the reorder; otherwise it falls back to position and tells you it did.
Will it catch a value that changed type?
Yes, and this is the one worth the page. 42 and "42" render identically, so anything comparing displayed values shows nothing at all. It is also an extremely common bug — a serialiser change, an ORM upgrade or a new gateway turning every integer ID into a string breaks clients that were doing a strict equality check, and nothing in the response looks different.
Does it handle large numbers correctly?
Yes, and most comparisons do not. A tool built on JSON.parse pushes every number through a double, so two different 19-digit IDs both round to the same value and are reported as equal. This one compares the digits as written. It also flags any number in either document that will change when something parses it.
What happens if one side has a duplicate key?
It is compared the way a parser would see it — last one wins — and you are told the repetition is there. That matters because the earlier value is invisible to the comparison and to every consumer of the document, which is exactly why a duplicated key survives so long unnoticed.
Can I compare JSON that does not quite parse?
Often, yes. Comments, trailing commas, single quotes, unquoted keys and Python's True, False and None are all recovered before comparison, so a tsconfig.json or a printed Python dict can be compared against real JSON. If a document is genuinely broken you get the line and column of the first problem rather than a blanket refusal.
How is a json diff different from a text diff?
A text diff compares lines, so it reports every reformatted line, every reordered key and every array item that moved. A json diff parses both sides first and compares the values, which is why it can say two differently written documents are identical and why it can catch a type change that looks like nothing. It is also why running a json compare online costs you nothing here: the parsing happens in your browser, so there is no server doing the work.
Is there a json compare online that does not upload my data?
This one. Every json compare online tool that shows you a result has to parse the documents somewhere, and most of them do it on a server — which means your two API responses have been sent to a third party before you see the answer. Here the parser is part of the page, so nothing is transmitted, and the tool works with the network disconnected.
Does it show a side-by-side view?
No — it shows a list of paths. For two large documents a side-by-side view means scrolling two panes hunting for coloured lines, while a list of six paths is the answer itself. Each entry is a JSONPath you can search for in your editor or paste into a ticket.
What is the difference between changed and moved?
Changed means the value at that path is different. Moved means the same item is present in both documents at a different index, which only appears when array matching is on identity. An item can be both, and then it is listed twice — once for the move and once for whatever changed inside it.
Is there a size limit?
No hard cap, but both documents are re-parsed and re-compared on every keystroke, so multi-megabyte inputs will feel sluggish in the textarea. Above about sixty differences the list is truncated on screen; copying the differences still gives you all of them.
Can I compare two JSON files rather than pasting?
Open them and paste. There is no upload, deliberately — the moment a page accepts a file it has a reason to send it somewhere, and the whole point here is that nothing is sent.
Why is this free, and what is Tekk?
Tekk is a spec-driven development platform for people building software with AI coding agents. This page costs nothing to run because nothing runs on our servers, so there is no signup wall, no run limit and no upsell inside the tool.

Want a real spec for what you’re building?

Drop a sentence. Tekk grounds it in your actual code and turns it into an executable plan.

Free to try · Connect GitHub during signup