JSON Diff — Compare Two JSON Files
100% in your browser
Your data is never uploaded. All processing happens locally on your device.
Read privacy policy →Last updated: 2026-09-17
Paste two JSON documents to see exactly what changed. The diff is semantic by default — key order does not matter. You get a change tree showing every added, removed, and modified path, plus a JSON Patch (RFC 6902) you can copy and a markdown report for pull requests. Everything runs locally in your browser.
What this tool does
Paste two JSON documents and get a semantic diff: every added, removed, and modified path, a JSON Patch (RFC 6902) you can apply programmatically, a markdown report for pull requests, and rendered HTML views for visual review. Key order is ignored by default. Arrays can be matched by index, by key field, or as unordered sets. Specific paths can be excluded with glob patterns. Everything runs locally in your browser — no data leaves your machine.
How to use
Paste left JSON
Paste your original or “before” JSON into editor A (left). You can also drag a file or use smart paste to auto-repair broken JSON.
Paste right JSON
Paste your modified or “after” JSON into editor B (right). Use the swap button to swap sides if needed.
Review the change tree
The default view shows a tree of every change with its path, type (added, removed, modified), and values. Click any change to jump to it.
Switch views or export
Switch to Side-by-side, Patch (RFC 6902), or Summary view. Export a markdown change report or copy the JSON Patch.
Example
Given a before document:
{
"id": "order-42",
"status": "pending",
"total": 99.90,
"items": [
{"id": "sku-1", "qty": 2, "price": 49.95},
{"id": "sku-2", "qty": 1, "price": 0}
],
"shipped": false
}And an after document:
{
"id": "order-42",
"status": "shipped",
"total": 99.90,
"items": [
{"id": "sku-1", "qty": 1, "price": 49.95},
{"id": "sku-3", "qty": 1, "price": 12.50}
],
"shipped": true,
"tracking": "1Z999"
}With array matching set to by-key (using the auto-detected id field), the change tree reports:
/status— modified: pending to shipped/items/0/qty— modified: 2 to 1/items/1— removed sku-2, added sku-3/shipped— modified: false to true/tracking— added: 1Z999
The /id and /total paths are absent because their values did not change. The second array element is reported as a remove plus an add because sku-2 was replaced by sku-3, not merely reordered. If the items were reordered without any new or removed SKUs, key matching would report moves instead.
Diff views explained
The tool produces four primary output formats, each suited to a different stage of review:
Tree view (HTML tree) — The default output. Every changed path renders as a collapsible node showing its old and new values side by side. Nodes are color-coded: green for additions, red for removals, amber for modifications. Nested objects and arrays can be expanded or collapsed so you can drill into a specific branch without scrolling through unrelated changes. Use this when you need to audit individual field-level changes, trace a regression to a single property, or present a reviewable hierarchy to a teammate unfamiliar with the raw JSON.
Side-by-side view (HTML) — Renders both documents formatted and syntax-highlighted, with changed lines marked in both panes. This is the view closest to a traditional code diff tool. The rendering is virtualized, so documents with thousands of lines remain scrollable without freezing the tab. Use this when you want a visual scan of the overall shape of the changes.
Patch view — Emits a JSON Patch array conforming to RFC 6902. Each entry is an operation — add, remove, replace, move, copy, or test — with a JSON Pointer path and a value where applicable. Copy the patch to your clipboard and apply it with any RFC 6902 library in Node, Python, Go, Rust, or any other language with a patch implementation. Use this when you need to transmit or persist a set of changes, or when you want to apply the same delta to multiple base documents.
Summary / markdown report — A compact text output listing the count of additions, removals, and modifications, followed by a markdown bullet list of each changed path with its old and new values. Paste this directly into a pull request description, commit message, or release note. Use this when you need a human-readable changelog rather than machine-readable patch operations.
Pick the view for your audience. The tree view suits an engineer auditing a regression; the patch view suits a downstream consumer applying the delta to another baseline snapshot; the summary view suits a reviewer scanning a pull request without toggling into verbose internals. You can switch views at any time without re-running the diff, because the underlying delta is computed once and every view is just a distinct rendering of that same intermediate result.
Array matching modes
Arrays are the hardest part of JSON diffing because the same logical item can appear at different positions in the two documents. The arrayMatch option controls how elements are paired before comparison:
By index (default) — Elements at the same array position are compared. Comparing [A, B] with [A, C] produces a single change at index 1. This is the right choice when array position is semantically meaningful — for example, a fixed-length coordinate pair like [lat, lng], or an ordered pipeline of processing steps where each slot has a specific role.
By key — Elements are matched by the value of a field within each object. The tool auto-detects the key field by checking for id, then _id, then uuid, then key, then name, in that order, using the first field that exists on the array elements. You can override this with the arrayKeyField option to use any other field. When two arrays contain the same objects in a different order, key matching reports moves rather than remove-plus-add pairs, which keeps the diff small and readable. Use this for arrays of objects that carry stable identifiers — line items in a shopping cart, rows in a database table, or entries in a feed.
As unordered set — Order is ignored completely. [A, B] and [B, A] are treated as identical. The diff reports only items that were added or removed, never positional changes. Use this for tag lists, permission sets, role assignments, or any collection where membership is the only thing that matters and sequence is irrelevant.
When array elements are objects and none of them expose any detectable key field, the by-key and set modes still function by falling back to comparing each element’s content as a whole. For key mode this means two objects with identical content are treated as the same row. For set mode, membership is decided by deep object equality rather than a field value, which works but is slower for large arrays and can misclassify near-duplicates if your rows differ only in a field you intended to ignore.
Ignoring paths
The ignorePaths option accepts glob patterns. Two wildcards are supported: * matches any single path segment, and ? matches a single character within a segment. Every node in the diff is checked against your patterns, and matched nodes are excluded entirely — they will not appear as added, removed, or modified.
Common patterns:
*.updatedAt— excludes any field namedupdatedAtat any nesting depth*.requestId— excludes request IDs wherever they appear in the treemeta.*— excludes every field directly under themetaobjectitems[*].price— excludes thepricefield on every element in theitemsarraycache.??— excludes any two-character key directly undercache
Separate multiple patterns with commas. Patterns are matched against the full dot-separated path of each node, so a.b.c targets a field c nested inside b inside a at the root level. Use this to strip out volatile fields like timestamps, hash values, request IDs, or cache entries that change on every request and would otherwise clutter the diff with noise. Note that ignoring a parent node also hides every descendant beneath it, which is usually what you want for an opaque blob like a signed token or a serialized payload you treat as a single unit.
Loose type comparison and float tolerance
Two options handle mismatches that strict equality would flag as changes but that are usually not meaningful:
looseTypeComparison — When enabled, the string "1" is treated as equal to the number 1, "42" equals 42, and "true" equals the boolean true. The comparison normalizes stringified scalars to their typed counterparts before checking equality. This is useful when one document came from a system that stringifies everything — URL query string parsers, certain CSV import pipelines, or older API responses that wrap all values in quotes. Disable this option when a type change is itself a meaningful difference you want the diff to surface, such as a field migrating from a string code to a numeric enum.
floatTolerance — Rounds floating-point numbers to a specified number of decimal places before comparing them. Without this, 0.1 + 0.2 produces 0.30000000000000004 in JavaScript, which would show up as a change from 0.3 to 0.30000000000000004 — a phantom difference caused by IEEE 754 representation error, not a real data change. Set the tolerance to the number of decimal places your values actually carry. Use this when comparing calculated totals, geographic coordinates, averages, or any value that has passed through floating-point arithmetic.
Do it in code
JavaScript, using jsondiffpatch — the same library this tool uses internally:
import { diff, formatters } from 'jsondiffpatch';
const left = { status: 'pending', total: 99.90, shipped: false };
const right = { status: 'shipped', total: 99.90, shipped: true };
const delta = diff(left, right);
// { status: ['pending', 'shipped'], shipped: [false, true] }
const patch = formatters.jsonpatch.format(delta);
console.log(JSON.stringify(patch, null, 2));
// [{"op":"replace","path":"/status","value":"shipped"},
// {"op":"replace","path":"/shipped","value":true}]Python, using the deepdiff library for diffing and jsonpatch for applying patches:
from deepdiff import DeepDiff
left = {"status": "pending", "total": 99.90, "shipped": False}
right = {"status": "shipped", "total": 99.90, "shipped": True}
result = DeepDiff(left, right, ignore_order=True)
print(result)
# {'values_changed': {"root['status']": {'new_value': 'shipped', 'old_value': 'pending'},
# "root['shipped']": {'new_value': True, 'old_value': False}}}Applying a JSON Patch in Python:
import jsonpatch
doc = {"status": "pending", "total": 99.90}
patch = [{"op": "replace", "path": "/status", "value": "shipped"}]
result = jsonpatch.apply_patch(doc, patch)
# {'status': 'shipped', 'total': 99.9}Common mistakes when diffing
- Forgetting that key order is ignored by default. If you need to detect key reordering as a change, use a text-based diff tool rather than this semantic diff.
- Matching arrays by index when the items were reordered between the two documents. This produces a cascade of false changes at every position after the first moved element. Switch to key-based matching.
- Not setting arrayKeyField when array objects lack any of the auto-detected fields (id, _id, uuid, key, name). Without a detectable key, key matching falls back to index matching silently.
- Comparing floating-point values without enabling floatTolerance. Totals and averages computed in different environments will show phantom changes from rounding error alone.
- Writing an ignorePaths pattern that is too broad. A bare
*matches every single-segment path and effectively disables the diff. Use specific patterns like*.updatedAtinstead. - Treating stringified numbers as real changes. If one document arrived via a URL query string or form encoder, enable looseTypeComparison so that
"1"and1are not reported as different. - Expecting move operations in the JSON Patch output when using unordered-set array matching. Set mode reports only additions and removals, never moves, because it discards position information entirely.
- Ignoring the difference between null and absent. A key set to null is a value; a missing key is an absence. The tree view distinguishes these as modified versus removed, but only if you do not conflate them in your source data.
Edge cases handled
- Empty objects and empty arrays are distinct types. Comparing
{}with[]is reported as a type change, not skipped as a no-op. - Null is treated as a present value, not as an absent key. A field that changes from
"hello"tonullis a modification; a field that disappears entirely is a removal. These are different operations in the diff and in the JSON Patch output. - Deeply nested structures are walked recursively with no fixed depth cap. The tree view collapses deep branches by default so you can expand only the paths you care about.
- Duplicate keys in input JSON are resolved by the browser native JSON.parse, which keeps the last value for each repeated key. The diff operates on the parsed result, so only the final value is compared. Clean up duplicates in your source data before diffing.
- Root-level arrays and root-level scalars are valid inputs. You can diff
[1, 2, 3]against[1, 2, 4], or even a bare string against another bare string, not just top-level objects. - Unicode content including emoji, CJK characters, and other multi-byte sequences is preserved through parsing, diffing, and all output views without corruption or escape artifacts.
- Inputs larger than 200KB are parsed and diffed inside a Web Worker, keeping the main thread free so the editor and view tabs remain interactive while the computation runs.
- NaN and Infinity values are not valid JSON and cannot appear in parsed input. If your source data contains them, it is not valid JSON and must be fixed before pasting.
- Mixed-type arrays such as
[1, "two", true]compare element by element, so a string at one slot and a number at the same slot in the other document register as a value change, not a structural mismatch. - Objects that contain a mix of scalar and nested members are handled transparently; the walker descends into nested objects and arrays while reporting scalar leaves as individual changes.