Polyatic JSON Diff

JSON Diff — Semantic Compare

Structurally diff two JSON documents by path — ignores key order and whitespace, reports added, removed and changed values live, and exports the result as an RFC 6902 JSON Patch. Nothing is uploaded.

Left · original
Right · new

Ignore array order: compare arrays as a multiset (a bag of values), so [1,2,3] equals [3,2,1]. Treat missing vs null distinctly: report a key that is null on one side and absent on the other (off = both count as absent).

Masked on both sides before diffing so volatile fields — timestamps, request ids — don't drown the real diff. One path per line, using the same dotted syntax the report shows: meta.updatedAt (nested key), items[0].id (array index), ["request id"] (a key with spaces). Array wildcards like items[*].id are not supported — name an explicit index. An unknown path is ignored (no-op); a malformed one is flagged and skipped. The RFC 6902 patch below reflects this masked comparison.

Paste JSON in both panes…
Diff report
JSON Patch (RFC 6902)
The RFC 6902 patch that turns the left document into the right one appears here.

Always generated order-sensitively with null ≠ missing — independent of the toggles above — so applying it to the left document yields the right one exactly. When Ignore fields are set, the patch is built from the masked documents, so it mirrors exactly the comparison shown above (the ignored fields are absent from it).

Runs entirely in your browser — neither document is uploaded. Both sides are parsed and compared on your device with the built-in JSON engine and a small script you can read; there are no network requests and no analytics on this tool.

Semantic diff, not a text diff

Paste a JSON document on the left and its new version on the right; the moment you stop typing, both are parsed into real values and compared by structure. Unlike a line-based diff — which compares the raw characters — this tool only reports differences in the data. Reordering an object's keys, re-indenting, or minifying makes no difference here, because objects are compared key-by-key and all whitespace is thrown away during parsing. Every difference is reported at its path, so you see exactly where in a deeply nested structure something changed, e.g. user.address.city: "NY" → "LA" or items[2].qty.

What each result means

  • Added — a key or array element present only in the right (new) document.
  • Removed — a key or element present only in the left (original) document.
  • Changed — a value that exists on both sides but differs; both the old and new value are shown.
  • Type mismatch — a changed value whose JSON type also differs, like the number 3 versus the string "3", flagged separately because it is a common source of bugs.

Worked example

Comparing these two documents:

left:  {"price": 9.99, "stock": 12,  "note": null}
right: {"price": 12.5, "stock": "12", "color": "blue"}

reports price changed 9.99 → 12.5; stock changed 12 → "12" with a type mismatch (number vs string); color added ("blue"); and — with the default settings — note shows nothing, because a null value and a missing key are treated as the same absence until you turn on treat missing vs null distinctly.

Key order and whitespace

JSON objects are unordered by definition (RFC 8259), so {"a":1,"b":2} and {"b":2,"a":1} hold identical data and are reported as equal here — a text diff would call them different. Arrays are the opposite: their order is part of the data, so by default a reordered array is a difference and each changed element is reported by index. When order genuinely does not matter — a list of tags, a set of ids — switch on ignore array order to compare arrays as multisets instead.

Ignoring volatile fields

Two API responses that are functionally identical often still differ in a handful of fields that change on every call — a generatedAt timestamp, a requestId, a signed URL with a rotating token. Those differences are real but useless, and they bury the one change you actually care about. Type the paths of those fields into Ignore fields, one per line, and they are masked (removed) from both documents before the diff runs, so neither the report nor the JSON Patch mentions them.

Paths use the same dotted notation the report prints, so you can read a path off the diff and paste it straight in: meta.updatedAt for a nested key, items[0].id for a specific array element, and ["request id"] (JSON-quoted) for a key that contains a space or dot. Every masked path is echoed back under the box as ignored: … so nothing is hidden from you. Two honest limits worth stating plainly: there is no array wildcarditems[*].id is rejected as unparseable rather than silently half-applied, so to drop a field from every element you must list each index; and masking an array index splices that element out of both sides (indices shift equally, so the remaining elements still line up). A path that doesn't exist in either document is simply a no-op, never an error, so you can keep a standing list of fields to ignore and reuse it across documents.

Exporting an RFC 6902 JSON Patch

Below the diff report the tool also emits the same differences as a JSON Patch — the standard machine-readable change format defined by RFC 6902. A patch is a JSON array of operations, each with an op (add, remove or replace here), a path, and for add/replace a value; applying the operations in order to the left document produces the right one. Paths are JSON Pointers (RFC 6901): /user/address/city names a nested key and /tags/0 the first array element. Two characters need escaping inside a pointer segment — ~ becomes ~0 and / becomes ~1 — so a key literally named a/b appears as /a~1b. For the price change in the worked example above, the patch is:

[
  { "op": "replace", "path": "/price", "value": 12.5 },
  { "op": "replace", "path": "/stock", "value": "12" },
  { "op": "remove",  "path": "/note" },
  { "op": "add",     "path": "/color", "value": "blue" }
]

Use a JSON Patch when a program, not a person, consumes the change: HTTP PATCH requests with the application/json-patch+json media type, Kubernetes kubectl patch --type=json, and most JSON document stores accept exactly this format. Two honest caveats. First, a valid patch needs exact positions and real absences, so it is always generated order-sensitively and with null ≠ missing, whatever the display toggles above are set to — a key that went from null to absent becomes a remove, while a value that changed to null becomes a replace. Second, array edits are expressed by index: inserting one element at the front of a 10-element array comes out as ten replace ops plus one tail add, not a single elegant insert. The patch is still correct — applying it reproduces the right document exactly — just not minimal for shifted arrays.

Honest limits

The diff report tells you what differs and the JSON Patch export gives you a machine-applyable version of it, but there is no three-way merge: this compares exactly two documents. Numbers are compared as JavaScript parses them, so 1.0 equals 1, and integers beyond 253 lose precision exactly as they would in any JavaScript program — keep very large ids as strings if you need them compared exactly. Both documents are held in memory and compared on the main thread, so extremely large files (tens of megabytes) may briefly freeze the tab. Everything runs locally: no third-party scripts, no network calls, so the two documents stay in the tab and are gone when you close it.