JSON Diff / Compare
Compare two JSON documents structurally — key order ignored, big numbers exact, RFC 6902 patch export — entirely on this device.
Key order is always ignored — the comparison is structural.
Drop a file into either pane — it is read on this device and never uploaded.
Press ? for shortcuts
Do it offline:$ diff <(jq -S . a.json) <(jq -S . b.json)
How to use it
- Paste the baseline document into pane A and the newer document into pane B — each side validates independently as you type, and a file dropped on a pane is read on this device, never uploaded.
- Read the result the moment both sides parse: a summary counts additions, removals, value changes and type changes, and the tree below marks each one with a + − ~ or ⇄ symbol.
- Turn on Ignore array order to match array elements as a multiset, or Numeric strings to let a numeric-shaped string equal its number — key order is structural and always ignored, so those two are the only equivalence options.
- Move between differences with the n and p keys or the visible next/previous buttons, switch to the linear list view for pointer-by-pointer reading, and filter to changes only when the documents are large.
- Copy the change report, download the differences as an RFC 6902 patch file, or share the whole comparison as a link — past 8 KB of compressed state the share dialog offers a state file instead.
Paste two JSON documents and see what actually changed — not how each happens to be printed. The comparison is structural: whitespace and key order never count, every difference is classified as added, removed, value-changed or type-changed at its exact location, and numbers compare by their source tokens, so 64-bit IDs cannot produce false diffs. Export the result as a plain-text change report or an RFC 6902 JSON Patch.
How it works
Each pane parses once into a columnar token table that records, for every value, its exact character range in the source text. The comparison then works over tokens, not floats: strings compare by decoded value, and numbers compare by canonical decimal token rather than by what JSON.parse returns. That last rule is what keeps 64-bit identifiers safe — 9007199254740993 and 9007199254740992 are different tokens even though JavaScript's number type collapses both into one float.
Objects compare key by key, which is why their order can never matter: RFC 8259 defines an object as an unordered collection of name/value pairs, and the walk looks a key up on the other side rather than at the same position. A key present on one side only becomes an addition or a removal; a key present on both recurses. A repeated key keeps its last occurrence, matching what JSON.parse does, with a visible warning. Arrays are order-sensitive by default — elements align through a longest-common-subsequence pass and the aligned pairs recurse — while Ignore array order matches elements by structural equality instead, multiset-style.
Every difference carries an RFC 6901 pointer, which is what the patch export serializes: an RFC 6902 operation list whose values are spliced from pane B's raw source text, never re-encoded, so exact decimals and 64-bit integers survive byte-for-byte. The honesty bounds are fixed and named: the listed changes cap at 5,000 behind a visible chip while the summary always counts everything, and an array alignment that outruns its time budget falls back to positional pairing behind another chip instead of freezing the tab.
Examples
{"service":{"name":"gateway","port":8080},"replicas":2}[
{ "op": "replace", "path": "/service/port", "value": 9090 }
]
One nested value changed, so the patch is a single replace operation addressed by the RFC 6901 pointer /service/port. Everything that matched stays collapsed in the tree — the summary counts exactly one change, marked with the ~ symbol.
[1,2,3][
{ "op": "replace", "path": "/0", "value": 3 },
{ "op": "replace", "path": "/2", "value": 1 }
]
Array order is significant by default, so positions 0 and 2 read as two value changes while the middle element matches. A chip points at the way out: “All differences disappear when array order is ignored.” With Ignore array order on, the same pair matches as a multiset — no differences, and an empty patch.
{"retries":"3","timeout":30}[
{ "op": "replace", "path": "/retries", "value": 3 }
]
The string "3" and the number 3 carry the same digits but different types, so the default verdict is one type change at /retries, marked ⇄. Because that option would erase every difference here, a chip offers it: “All differences disappear when numeric strings compare as numbers.”
{"id":9007199254740993,"active":true}[]
Both documents carry the 64-bit ID 9007199254740993 — one minified, one pretty-printed. JSON.parse rounds that literal to 9007199254740992, so a float-based comparison is one step from a wrong answer; here numbers stay raw tokens, only the bytes differ, and the verdict is an empty patch plus the chip “The documents are structurally identical — they differ only in formatting (whitespace, key order, or number/string spelling).”
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Duplicate keys keep the last value
A document can legally repeat a key, and most parsers quietly keep the final occurrence. Both panes follow that parser reality — last one wins — and say so out loud: a warning names the repeated key and the lines it sits on, quoted under common errors below, so the comparison matches what your program would actually read.
Disjoint documents stay responsive
Compare two documents that share almost nothing and nearly every node is an addition or a removal. Rather than building a hundred-thousand-row DOM, the viewer lists the first 5,000 changes behind a chip ending in “the summary counts every change” — the totals stay complete even when the listing is capped, and the exported report says so too.
An empty pane is idle, not an error
Until both sides hold parseable JSON there is nothing to compare, so a blank or whitespace-only pane simply waits — no red banner, no zeroed-out summary. The diff runs the instant the second document parses, and a parse failure in one pane never disturbs the other.
Mismatched top-level types are a result
An array on one side and an object on the other is a legitimate answer, not a crash. The tool reports one type change at the document root and adds the note “A is an array, B is an object — the top-level types differ.” — useful when an API starts wrapping a list in an envelope.
Size and speed
Each pane accepts up to 25 MB. Past 256 KB combined the comparison moves to a background worker, and past 5 MB combined a progress readout with a Cancel button takes over, so multi-megabyte API exports diff in seconds while the page stays interactive throughout.
Share links have an 8 KB budget
A share link packs both documents, compressed, into the URL fragment — the part of an address a browser never sends to a server. Two real documents overflow that quickly, so beyond 8 KB compressed the dialog offers a state file download instead, and either pane's file input re-opens it later.
Common errors
Unexpected '}' at line 1, column 24 — probable cause: trailing comma after the last property.- A parse failure blocks only its own pane — the other side stays validated and the diff simply waits. The message pins the exact line and column with a code frame, and the one-click hand-off to JSON Repair can delete the comma for you.
Input is larger than the 25 MB limit for this tool.- The per-side memory guard. Nothing is trimmed to fit: an oversized document is declined whole, whatever sits in the other pane stays untouched, and the CLI line below handles files of any size your own machine can hold.
duplicate key `id` at lines 2 and 3 — most parsers keep the last- A warning on valid JSON, not an error. The pane collapses the repeated key to its final value — the same value JSON.parse returns — so the verdict reflects the data your code sees, and the warning keeps the collapse visible instead of silent.
Pane A input is larger than the 25 MB limit for this tool. For bigger files use the CLI: diff <(jq -S . a.json) <(jq -S . b.json)- The page-side refusal, shown before any parsing starts, naming the limit and this page's own command-line equivalent. One honest caveat: jq re-emits numbers through its own parser, and depending on the jq version a 64-bit ID like 9007199254740993 can come back rounded — this page compares raw tokens and cannot round. Refusing never clears either pane.
FAQ
What makes a structural JSON diff different from a text diff?
A text diff compares lines of characters, so re-indenting a file or reordering its keys lights up nearly every row. A structural diff parses both sides first and compares values: formatting vanishes, and each real difference is classified — added, removed, value-changed, type-changed — at an exact pointer. When you do want line-by-line output, this page has a text-mode fallback and a link to the dedicated text tool.
Why does comparing [1,2,3] with [3,2,1] report two changes?
Because array order is meaningful in JSON, elements align in sequence by default: positions 0 and 2 changed while the middle 2 matched. When order is noise rather than signal — tags, id sets, unordered results — switch on Ignore array order and the elements match as a multiset instead; that pair then reports no differences at all.
Can it compare documents with 64-bit IDs without rounding them?
Yes. Numbers compare as canonical decimal tokens taken from the source text, never as JavaScript floats, so 9007199254740993 stays distinct from 9007199254740992 — a pair JSON.parse collapses into a single value. The patch export splices the same raw tokens, so an exact literal survives the whole round trip untouched.
What exactly is the JSON Patch file this page exports?
An RFC 6902 document: an ordered list of add, remove and replace operations, each addressed by an RFC 6901 pointer, that transforms document A into document B. This page emits patches; applying one is a job for a patch library today — a dedicated patch-application tool ships here later.
Does anything I paste here leave this device?
No. Parsing, diffing and every export run inside this browser tab — watch the network panel while comparing and no request carries your documents. A share link keeps the comparison state in the URL fragment, which a browser does not transmit to any server.
How large can each document be?
25 MB per pane, and the bound is a refusal, not a truncation — an oversized side is declined whole with the limit named. Above 256 KB combined the work runs in a background worker, the changes-only filter switches itself on to keep the result navigable, and above 5 MB combined you get progress and a Cancel button.
Why is key order always ignored, with no option to count it?
Because RFC 8259 defines a JSON object as an unordered collection of name/value pairs, two documents whose keys merely sit in a different sequence are the same data — a diff that flagged them would be reporting formatting, not meaning. When formatting is the only difference, this page says so explicitly in a note instead of inventing changes.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Re-run the comparison immediately, at any input size.
- ⌘/Ctrl + S
- Download the RFC 6902 patch file for the current result.
- ⌘/Ctrl + C
- Copy the change report when no text is selected; a selection still copies itself normally.
- ⌘/Ctrl + Shift + C
- Copy the change report even while a selection is active.
- s
- Swap panes A and B — added and removed trade places, and one undo restores the original sides.
- n
- Jump to the next change; a visible button does the same.
- p
- Jump back to the previous change.
- t
- Cycle to the next theme.
- ? or ⌘/Ctrl + /
- Open the shortcut help overlay.
- Esc
- Dismiss the topmost overlay; both panes keep their contents.