██╗███████╗ ██████╗ ███╗ ██╗ ██████╗ ██╗███████╗███████╗███████╗██████╗ ███████╗███╗ ██╗ ██████╗███████╗ ██╗ ██╗██╗███████╗██╗ ██╗███████╗██████╗
██║██╔════╝██╔═══██╗████╗ ██║ ██╔══██╗██║██╔════╝██╔════╝██╔════╝██╔══██╗██╔════╝████╗ ██║██╔════╝██╔════╝ ██║ ██║██║██╔════╝██║ ██║██╔════╝██╔══██╗
██║███████╗██║ ██║██╔██╗ ██║ ██║ ██║██║█████╗ █████╗ █████╗ ██████╔╝█████╗ ██╔██╗ ██║██║ █████╗ ██║ ██║██║█████╗ ██║ █╗ ██║█████╗ ██████╔╝
██ ██║╚════██║██║ ██║██║╚██╗██║ ██║ ██║██║██╔══╝ ██╔══╝ ██╔══╝ ██╔══██╗██╔══╝ ██║╚██╗██║██║ ██╔══╝ ╚██╗ ██╔╝██║██╔══╝ ██║███╗██║██╔══╝ ██╔══██╗
╚█████╔╝███████║╚██████╔╝██║ ╚████║ ██████╔╝██║██║ ██║ ███████╗██║ ██║███████╗██║ ╚████║╚██████╗███████╗ ╚████╔╝ ██║███████╗╚███╔███╔╝███████╗██║ ██║
╚════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝╚══════╝ ╚═══╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝
⚙ Comparison options array matching · ignore rules · tolerance
JSON Diff Viewer — the complete FAQ
Everything below is about comparing JSON as data rather than as text: how the two documents are walked, why arrays are the hard part, how to silence the fields that change on every request, and what the six output views are each good for. No sign-up, no upload — the comparison happens in this tab.
Nothing here is a substitute for reading the specs when it matters: JSON Patch is RFC 6902, JSON Merge Patch is RFC 7386, and the pointer syntax inside a patch is RFC 6901.
01. Getting started
What does JSON Diff Viewer do?
Paste or drop two JSON documents and it shows you exactly what changed: every added, removed and changed value, in a colour-coded collapsible tree. From the same comparison it can also produce a unified text diff, an RFC 6902 JSON Patch, an RFC 7386 merge patch, a flat list of changed paths, or a Markdown report you can paste into a pull request. Everything runs in your browser.
I have nothing to paste. Can I try it?
Yes — the example buttons above the editors load five ready-made pairs: an API response that gained and lost fields, a package.json with version bumps, a Kubernetes manifest with a changed image and resource limits, a reordered array that looks completely different until you switch array matching to “by key”, and a noisy pair where the only real change is buried under timestamps and request IDs.
Which example shows the point of the tool best?
Reordered array. Compared by position it reports every element as changed; switch Arrays are matched to “by a key inside each item” and the same two documents turn out to be identical apart from one field. That gap — between a diff that is technically correct and a diff that is useful — is most of why this tool exists.
Is my data uploaded anywhere?
No. There is no server, no upload endpoint and no storage. The parsing and the comparison happen in JavaScript in your tab. You can check: open the network panel, load a file, run a comparison, and watch it stay empty. That matters because the JSON people most want to diff — API responses, Terraform state, Kubernetes secrets, exported config — is usually the JSON they are least allowed to paste into a website.
Does it work offline?
Yes, once the page has loaded. The whole tool is one HTML file with no external calls at runtime, so you can disconnect and keep working — on a plane, on a jump box, or inside a network that blocks everything.
How do I load a file?
Three ways: the 📁 File button above each editor, dragging a file straight onto the text area, or plain paste. Drag-and-drop is usually fastest when you already have both files open in a file manager.
Are there keyboard shortcuts?
Ctrl/⌘ + Enter runs the comparison from anywhere on the page, and Ctrl/⌘ + S downloads the current view instead of saving the web page. If you are iterating on ignore rules, Ctrl+Enter is the whole loop.
What do the numbers under each editor mean?
They describe the document you pasted, before any comparison: whether it is an object or an array, how many keys it contains in total, how deeply it nests, and how large it is in kilobytes. It is a quick sanity check — if you meant to paste a 4 MB response and the box says 0.2 KB, you truncated it somewhere.
Is it free?
Free, no account, no sign-up, no limit imposed by us. It is one of a set of client-side developer tools at jasperbernaers.com/apps.
02. How the comparison works
What exactly counts as a difference?
The two documents are walked together, key by key and element by element. At every position one of five things is true: the value exists only on the right (added), only on the left (removed), exists on both but differs (changed), exists on both and matches (unchanged), or was excluded by an ignore rule (ignored). Containers are recursed into; leaves are compared directly.
Does key order matter?
No. In JSON an object is an unordered set of name/value pairs, so {"a":1,"b":2} and {"b":2,"a":1} are the same document and this tool reports no difference. That is one of the main reasons a text diff is the wrong tool for JSON: git diff will happily show you two rewritten lines where nothing changed.
Does whitespace or indentation matter?
Not at all. Both sides are parsed into values before anything is compared, so minified JSON and pretty-printed JSON of the same data are identical. If you want them to look the same too, hit { } Format on either side.
Does the type of a value matter?
Yes, and deliberately so. "1" and 1 are a change, as are 0 and false, and null and "". Loose equality hides exactly the class of bug you are usually hunting — a field that quietly became a string when it crossed a serialisation boundary.
How is “changed” different from “removed then added”?
A change is reported when the same path exists on both sides with different values, and you see the old and the new value on one line with an arrow between them. Removed-then-added means the path exists on only one side. The distinction is what makes the tree readable: a renamed key shows as one removal and one addition, which is the honest description of what happened.
What happens with deeply nested documents?
They are compared to any depth — the recursion has no artificial limit, only your available memory. In the tree every container is collapsible, so a five-level-deep change is one click away rather than a scroll.
Are numbers compared exactly?
By default yes, using JavaScript's own number equality after parsing. Because JSON numbers become IEEE 754 doubles, 0.1 + 0.2 style artefacts can make two “equal” numbers differ in the last bits — set a numeric tolerance if that is happening to you.
What about very large integers?
This is a real trap. JSON.parse produces doubles, so any integer beyond 253−1 (9,007,199,254,740,991) loses precision — a Twitter-style 64-bit ID or a Postgres bigint can be silently rounded, and two different IDs can compare as equal. If your API returns large IDs, have it serialise them as strings; that fixes this tool and every other JavaScript consumer at the same time.
How does it handle duplicate keys in one object?
The same way every JSON parser does: the last occurrence wins and the earlier ones vanish before the comparison ever sees them. Duplicate keys are legal to write but the spec leaves the behaviour undefined, so a document that relies on them is already broken. If a diff looks impossibly empty, check for a repeated key.
Is the comparison symmetric?
Structurally yes, but the labels are not: additions and removals swap when you swap the sides, because “added” means “present on the right”. Use ⇄ Swap when you realise you pasted the new version on the left — the counts stay the same and only the direction flips.
03. Arrays — the hard part
Why do arrays need special treatment?
Because JSON gives you no way to say what an array means. Sometimes position is the identity — the coordinates in a GeoJSON polygon, the steps of a pipeline. Sometimes position is meaningless — a list of users returned in whatever order the database felt like. A diff tool cannot know which you have, so this one lets you say.
What does “matched by position” do?
Element 0 is compared with element 0, element 1 with element 1, and so on; if one array is longer the extra elements are additions or removals. It is the literal, spec-faithful reading and the right default. It is also why inserting one item at the top of a list of 200 produces 200 changes.
What does “matched by a key inside each item” do?
Each element is identified by one of its own fields rather than by where it sits. Items with the same key value are compared to each other wherever they appear, items whose key exists only on the right are additions, only on the left removals. Reordering then produces no diff at all, which is almost always what you want for a list of records.
Which key does auto-detect pick?
It tries id, _id, uuid, key, name, slug, code, sku and email, in that order, and accepts the first one that is present on every element and unique across them. If nothing qualifies it falls back to position for that array rather than guessing. Type the field name into Match key to override it.
What if different arrays in my document need different keys?
Auto-detect handles that case: it is applied per array, so a list of users keyed by email and a list of products keyed by sku both work in the same run. An explicit key applies everywhere, so use it only when auto-detect gets one array wrong.
What does “as an unordered set” do?
Elements are matched by deep equality of their whole value. Anything present on both sides is unchanged no matter where it sits; the rest are additions and removals. It answers “which items joined and which left” and is the right mode for a list of tags, permissions, IP allow-list entries or enabled feature flags.
When would set matching mislead me?
When items can be modified. A user record whose email changed is not “one removed, one added” in any useful sense, but that is what set matching reports, because the two values are no longer deeply equal. If elements have identity and can change, match by key instead.
How do I diff two arrays of objects that have no ID at all?
Options, roughly in order of effort: sort both sides by some stable field before pasting (jq 'sort_by(.name)'), use set matching if you only care about membership, or nominate a field that is unique in practice even if it is not called id — a compound value you build yourself in jq works fine.
Why does my array of numbers show every element as changed?
Something was inserted or removed near the front and you are matching by position, so everything after the insertion point shifted. Switch to set matching to see the true membership change — usually one addition — or accept the shift as the honest answer if position really is meaningful in your data.
Are nested arrays handled?
Yes, at any depth, and the array mode applies at every level. An array of objects each containing an array of tags works the way you would hope: the outer list matched by id, the inner tags compared under the same rules.
04. Ignore rules, tolerance and noise
Why would I want to ignore parts of the document?
Because real payloads are full of fields that change on every call and mean nothing: timestamps, request IDs, trace IDs, cache headers, generated ETags, expiry counters. If half your diff is noise you will stop reading it. Ignoring those paths turns a hundred-line diff into the two lines that matter.
How do I write an ignore rule?
Comma-separated paths in the Ignore these paths box. A bare word like timestamp matches that key wherever it appears. A dotted path like meta.requestId matches that exact location. * stands for exactly one segment and ** for any number, so items.*.updatedAt ignores that field on every item and meta.** ignores everything under meta.
What happens to an ignored value?
It is shown greyed out and marked · in the tree, counted separately in the summary line, and excluded from the JSON Patch, the merge patch and the path list. Ignored, not hidden — you can always see that a rule fired, which stops you from quietly ignoring the one field that mattered.
Do ignore rules apply inside arrays?
Yes. Array indices are ordinary path segments, so orders.0.total targets one element and orders.*.total targets the field in every element. With key matching the segment is the key value rather than the index, which is more stable to write rules against.
What is the numeric tolerance for?
Treating numbers within a given absolute distance as equal. Set it to 0.0001 and a price of 19.99 against 19.990000000000002 stops being a change. This is a floating-point problem, not a JSON problem — it appears whenever a number has been through arithmetic on either side.
Should I use tolerance on money?
Ideally you would not need to: store money as integer minor units, or as a string, and compare it exactly. If you have inherited floats then a tolerance of half your smallest unit is a reasonable stopgap — but be aware you are now unable to see a one-cent error, which is sometimes precisely the bug.
What does “ignore case in string values” affect?
String values only, never keys. "Active" and "active" stop counting as a change. Useful when comparing across systems that normalise casing differently; risky when case carries meaning, as it does in enum values, base64 payloads and most identifiers.
What does “ignore leading and trailing whitespace” do?
Trims string values before comparing, so "Brussels " equals "Brussels". Trailing spaces are a classic artefact of CSV imports and copy-paste, and they are invisible in the tree — which is why the option exists rather than you staring at two identical-looking strings.
What does “treat null and a missing key as the same” do?
It makes an explicit null equal to an absent key. Whether that is right depends entirely on your API: in a PATCH body null usually means “clear this field” and absence means “leave it alone”, and conflating them is a bug. In a serialiser that omits empty fields, they mean the same thing and the option removes a whole column of false differences.
Do the options change the exported patch?
Yes — every view is generated from the same comparison, so the JSON Patch and merge patch reflect exactly the options that were active. Ignored paths produce no operations. That is convenient, and it is also a reason to check the options panel before you hand a patch to something that will apply it.
Can I save my options?
Not between reloads — nothing is persisted, deliberately. The noisy example loads a working ignore rule you can adapt, and in practice a rule you can retype in ten seconds is less trouble than one you forgot you had switched on.
05. The six output views
What are the views for?
One comparison, six ways of reading it. Tree for exploring, Unified for a familiar +/− text diff, JSON Patch for something a machine can apply, Merge Patch for a PATCH request body, Path list for grepping and for tickets, and Markdown for pasting into a pull request. Switching views never recomputes the diff, so it is instant.
How do I use the tree view?
Click any container line to collapse or expand it, use hide unchanged to strip the document down to just what moved, and type in the filter box to keep only paths or values containing that text. On a large document the usual sequence is: hide unchanged, collapse all, then expand the one branch you care about.
What does the filter box match?
The path and both values of each line, case-insensitively, as a plain substring — not a regular expression. A parent stays visible when any descendant matches, so filtering for price keeps the structure around it rather than dumping a flat list.
What is the unified view?
The diff rendered as text with +, - and ~ markers, the shape everyone already knows from git diff. It is the best view to paste into a chat message, and combined with hide unchanged it is usually short enough that people will actually read it.
What is the path list view?
One line per difference, with no structure at all: + user.address.city = "Ghent". It is the view to use when you want to count changes, grep them, sort them, or paste them into a ticket as a checklist — and the fastest way to answer “did anything under billing change?”
What is the Markdown view?
A short report with a summary line and grouped tables of additions, removals and changes, ready to paste into a pull request description, an incident write-up or a release note. It renders on GitHub, GitLab and most wikis without editing.
What does the summary line tell me?
The percentage of compared values that differ, how many values were compared in total, how many were ignored, and how long the comparison took. The percentage is a good smell test: 2% usually means a targeted change, 90% usually means you are comparing the wrong two things — or matching arrays by position when you should be matching by key.
Can I copy or download a view?
📋 Copy puts the current view on your clipboard and ⬇ Download saves it, choosing the extension to match — .json for the two patch views, .md for the report, .diff otherwise. Both always give you exactly the view on screen, including the effect of hide-unchanged and the filter.
Why is my downloaded file empty?
You downloaded before comparing. The views are generated from the last comparison, so run one first — Ctrl/⌘ + Enter is quickest.
06. JSON Patch and Merge Patch
What is a JSON Patch?
RFC 6902. A JSON array of operations — add, remove, replace and a few more — each naming a target with a JSON Pointer. It is precise, it can express “delete this key” unambiguously, and it can address individual array elements. It is what you send to an API that advertises application/json-patch+json.
What is a JSON Merge Patch?
RFC 7386. A document shaped like the original where every value you supply replaces the corresponding value, and an explicit null deletes a key. Far easier to read and write than JSON Patch, which is why most hand-written PATCH bodies are merge patches whether or not anyone says so.
Which one should I use?
Merge patch when the change is “set these fields to these values”, which is most of the time. JSON Patch when you need to delete a key whose new value could legitimately be null, when you must modify one element of an array without resending the whole thing, or when the order of operations matters. Above all, use whichever your server accepts — sending the wrong one usually fails silently in the most confusing possible way.
Why can't merge patch delete an array element?
Because merge patch has no way to address one element: any array you supply replaces the whole array. That is the deliberate trade for its simplicity, and it is the single most common reason people move to RFC 6902.
Why can't merge patch set a field to null?
Because null is reserved as the delete instruction. There is no escape hatch — if null is a meaningful value in your domain, merge patch cannot express it and you need JSON Patch, where {"op":"replace","path":"/x","value":null} says exactly what it says.
What is a JSON Pointer?
RFC 6901, the addressing syntax inside a JSON Patch: /user/address/city, with array indices as segments — /items/0/price. Two characters are escaped because they are structural: ~ becomes ~0 and / becomes ~1. So a key literally named a/b is addressed as /a~1b. The generated patch handles this for you; it is worth knowing when you read one by hand.
Can I apply the generated patch?
Yes, with any conforming library — fast-json-patch or rfc6902 in JavaScript, jsonpatch in Python, json-patch in Go, Json.Patch in .NET. Applying it to the left-hand document should reproduce the right-hand one exactly, which is a useful way to check your ignore rules did not hide something.
Will the patch round-trip if I used ignore rules?
No, and that is the point of ignoring: excluded paths generate no operations, so applying the patch leaves those fields as they were. Perfect for “apply the real change, leave the timestamps alone”, wrong if you expected an exact copy. Clear the ignore box and re-run when you need a faithful patch.
Does the patch include tests?
No test operations are emitted. RFC 6902 lets you assert a value before changing it, which is how you build optimistic concurrency, but a test asserting a value that may legitimately have moved on turns a patch into a landmine. Add tests deliberately if you need them.
How do array operations appear in the patch?
RFC 6902 addresses array elements by index, and every operation shifts the indices of everything after it — which is why hand-written array patches are so often wrong. When the element operations happen to be index-safe (same positions, or items appended at the end) you get them individually. When they are not — a reorder, a removal, an insertion in the middle — the whole array is emitted as a single replace, because that is the version that actually applies. The tree still shows you the element-level detail; only the patch is coarsened.
Can I check the generated patch is right?
Apply it to the left-hand document with any RFC 6902 library and compare the result to the right-hand one — it should match exactly, provided you cleared the ignore rules first. The examples in this tool are checked that way, and the array-replace behaviour above exists because the naive version failed that check.
07. Real-world JSON
Can I paste JSON with comments or trailing commas?
Strict JSON.parse rejects both, but when it does the error line offers a try repair button. Repair strips // and /* */ comments, removes trailing commas, converts single-quoted strings and unquoted keys, and replaces NaN and Infinity — enough to rescue a tsconfig, a copied JavaScript object literal or a hand-edited config file.
Is repair safe?
It is a best-effort text transformation, not a parser, so read the result before trusting it — a // inside a URL string is the classic case it can mangle. It rewrites the text in the editor so you can see exactly what it did, and undo is one Ctrl+Z away.
Can I diff NDJSON or JSON Lines?
Not directly — each line is a separate document. Wrap them first: jq -s '.' events.ndjson > events.json turns the stream into one array, which you can then match by key on whatever field identifies an event.
Can I diff JSON with a top-level array?
Yes. Arrays, objects, strings, numbers, booleans and null are all valid JSON documents and all compare fine. A top-level array is exactly the case where the array-matching mode matters most.
Can I diff YAML, TOML or XML?
Convert first. yq -o=json for YAML, yj -tj for TOML, xq for XML. This is usually worth it even beyond this tool: comparing YAML as data rather than as text removes every difference caused by quoting style, anchors and indentation.
What is a good way to compare two API responses?
curl -s $A | jq . > a.json and the same for B, then drop both files in and ignore the volatile fields. If you do it often, a two-line shell function is worth writing — the hard part is never fetching, it is remembering which fields are noise.
How do I compare only part of a large document?
Slice it before pasting: jq '.data.items' big.json. A smaller comparison is faster, and much easier to read than the same change buried eight levels down. The filter box does something similar after the fact, but pre-slicing is better when the document is genuinely huge.
How should I diff package-lock.json?
Expect thousands of differences and do not read them in a tree. Use the path list view, filter to version, and you have the actual answer — which packages moved. For the security question, npm audit and npm ls are the right tools; a diff tells you what changed, not whether it matters.
How should I diff Kubernetes manifests?
Ignore the fields the cluster owns — metadata.managedFields, metadata.resourceVersion, metadata.uid, metadata.creationTimestamp, status.** — and what remains is your change. Match containers by name rather than position, because manifests reorder them freely. The Kubernetes example loads a pair set up this way.
Can I use it to review a config change before deploying?
That is one of its best uses: paste what is running and what you are about to apply, ignore the generated fields, and read the path list. It takes a minute and it catches the class of mistake that no test catches — a value you edited in the wrong environment's file.
Can I compare two versions of the same file from git?
Yes: git show HEAD~1:config.json > old.json and git show HEAD:config.json > new.json, then load both. Compared with git diff, you get key-order independence, array matching by key, and ignore rules — the three things that make a JSON diff readable.
08. Privacy, limits and troubleshooting
How do I know the data really stays local?
Open your browser's network panel and use the tool. No requests appear, because there is nothing to request — the page is a single HTML file with the parser, the differ and the renderers inside it. You can also save the page and open it from disk with your network off.
Is anything stored between sessions?
No. Nothing is written to localStorage, sessionStorage, IndexedDB or cookies. Reload and both editors are empty, which also means nothing is left behind on a shared machine.
What about analytics on the site?
Privacy-first page-view counting with no cookies and no personal data. It records that the page was viewed. It has no access to what you paste, which never leaves the JavaScript in your tab.
Should I paste production secrets in here?
Technically the data never leaves your machine, so it is safer than any hosted diff service. But a good habit is a good habit: redact tokens and keys before pasting anywhere, because the risk is rarely the tool — it is the screenshot, the screen share, or the tab you leave open in a meeting.
How large a document can it handle?
There is no hard cap; the limits are your browser's memory and how long you are willing to wait for rendering. A few megabytes on each side is comfortable. Documents in the tens of megabytes will parse but the tree becomes unpleasant to navigate — use hide unchanged, or slice the document with jq first.
Why did my browser tab freeze?
Almost always rendering rather than comparison: a diff with tens of thousands of visible lines produces tens of thousands of DOM nodes. Switch on hide unchanged before comparing large documents, or use the path list view, which is dramatically lighter.
The comparison says everything changed. What went wrong?
Three likely causes, in order: arrays are being matched by position when they should be matched by key; the two sides are not the same kind of document (one wrapped in an envelope, the other not); or one side is a string containing JSON rather than JSON itself. The percentage in the summary line is the tell — a real change is rarely above a few percent.
One side is a string containing escaped JSON. What now?
That happens when a field carries a serialised payload, and it appears as one enormous changed string. Unwrap it first: jq -r '.payload' resp.json | jq . gives you the inner document, which is the thing you actually want to diff.
It says my JSON is invalid but it looks fine.
The error message names the line. The usual culprits are a trailing comma before } or ], single quotes instead of double, an unquoted key, a smart quote from a word processor, a stray BOM at the start, or a truncated copy that is simply missing its closing brace. Try repair fixes most of them in one click.
Two values look identical but are marked as changed.
Something invisible differs. In order of likelihood: a trailing space (turn on whitespace trimming), a type change from 1 to "1", a floating-point tail beyond the digits shown, or a Unicode look-alike — a non-breaking space, a smart quote, or a combining accent against a precomposed one. The Markdown view quotes values in a way that makes stray spaces visible.
Does it work on a phone?
Yes, in any modern mobile browser. The layout stacks the editors vertically. Pasting a large document into a mobile text area is its own kind of unpleasant, but reading a diff someone sent you works fine.
Which browsers are supported?
Any current Chrome, Edge, Firefox, Safari, Brave, Opera or Vivaldi. No frameworks, no external libraries, no build step — just the page.
How is this different from running diff on two files?
diff compares lines of text, so it reports key reordering and reformatting as changes and cannot see that two differently indented documents are identical. This compares parsed values, so it reports only real differences — and it can match arrays by key, ignore paths, and emit a patch, none of which a line differ can do.
Can I automate this in CI?
Not this page, but the same ideas: jd, json-diff and dyff are good command-line differs, and jq -S . a.json > a.sorted.json plus an ordinary diff gets you surprisingly far. This tool is for the moment you are staring at two payloads trying to work out what broke.