Why don't reordered object keys show up as changes?
Because they are not part of the JSON data model. After `JSON.parse`, `{"a":1,"b":2}` and `{"b":2,"a":1}` are the same object — JavaScript engines preserve insertion order for string keys (ECMAScript 2020+) but the JSON spec itself does not promise any order, and every consumer that reads the parsed value gets the same answer regardless of how the source happened to be authored. Treating reordered keys as changes would mean the tool reported as "different" two payloads that no JSON consumer can distinguish, which is the kind of false positive that conditions reviewers to ignore the diff.
Why do array changes look so noisy when I added one item at the start?
Because we compare arrays by index, not by identity. Inserting `"NEW"` at position 0 of `["a","b","c"]` gives `["NEW","a","b","c"]`, and under index comparison position 0 is now `"NEW"` instead of `"a"`, position 1 is `"a"` instead of `"b"`, position 2 is `"b"` instead of `"c"`, and position 3 is `"c"` (added). This is honest about what we did — index comparison is fast, deterministic, and produces the same answer every time. Move-aware diffs (LCS, Myers, Hungarian-algorithm matching) take a position on what counts as a "moved" element vs what counts as a "removed-and-re-added" element, and that decision is application-specific. If you need moves detected, the right tools are `jsondiffpatch` (browser-friendly, emits its own delta format) or `deep-diff` / `microdiff` in Node — those let you describe the array's identity contract first and then ask for a diff under that contract.
What does `type_changed` mean and why doesn't it descend into the new shape?
A `type_changed` row says "the kind of value at this path swapped" — a number became a string, a string became `null`, an object became a primitive, an array became an object. The two subtrees are no longer comparable in any meaningful way: descending into the new shape would emit one `added` row for every leaf the new shape has and one `removed` row for every leaf the old shape had, which is correct in a pedantic sense but misleading in practice — the change reviewers care about happened at the boundary, not at the leaves. We emit one row at the swap and stop. If you want the leaf enumeration, paste each side into a JSON formatter and read it manually; the cases where this matters are rare enough that we did not add a "show subtree on type_changed" toggle.
Are duplicate keys in a JSON object detected?
No, and they cannot be detected after `JSON.parse`. The JSON spec leaves duplicate-key behaviour "implementation-defined," and every browser we support (Chromium, Firefox, WebKit) keeps the LAST occurrence of a duplicated key and silently discards the earlier ones. By the time the diff walker sees the parsed value, the duplicates are gone — there is no API to ask the parser for them. If duplicate-key detection matters for your workflow, the right approach is a streaming parser (`json-source-map`, `JSON5` for relaxed grammar, `parse-json` for line/column-aware errors) on top of the raw text, before the data structure is built. This tool is built on the parsed object, on purpose, because the parsed object is what every downstream consumer of the JSON actually sees.
Are paths stable, and what does the format look like?
Yes — the path format is JSON Pointer (RFC 6901). The root is the empty string. Top-level keys are `/key`. Array indices are decimal integers without a leading zero (`/items/0`, never `/items/00`). Object keys that contain `/` or `~` are escaped per the RFC: `/` becomes `~1`, `~` becomes `~0`. So a key like `a/b` reads as `/a~1b`, and a key like `c~d` reads as `/c~0d`. The tool emits the same path format that an HTTP PATCH (RFC 6902) consumer, a JSON Schema `$ref`, or a `jsonpointer` library would use, so you can paste a returned path into any pointer-aware reader and get the same value.
How does this compare to `diff` on pretty-printed JSON?
A textual `diff` on the output of `jq -S` (sort keys then pretty-print) gets you most of the way for small payloads — the sort-keys flag fixes the reordered-keys false positives, and the pretty-print flag fixes the whitespace false positives. But the textual diff still treats arrays as line-of-text comparisons (so a head insertion still shows every later position as changed, exactly like the index comparison here), it cannot tell you "this changed type" from "this got removed and a new value appeared at the same place," and it does not give you machine-readable paths. This tool is built for the case where you want to know "exactly which paths changed" and want the answer in a format you can paste back into a Postman test, a `jq` selector, or a controller breakpoint without converting between path notations.
What happens when one side is invalid JSON?
The status bar tells you which side failed and why — `Original JSON is invalid — <parser message>` or `Changed JSON is invalid — <parser message>`, with the parser's own line/column information. The page does not try to "fix" malformed JSON or to do partial diffs across an invalid boundary; if you need to clean up the source first, paste it into the JSON formatter, fix the error there, then come back. The honest contract is that diff requires two valid JSON values — anything else is a parser problem, not a diff problem.
Is there a file size limit?
Each side stays under roughly 10 MB. The walker holds both parsed values in memory before emitting the change list, so a 50 MB session log on each side will stall on lower-RAM devices. For multi-megabyte payloads the right tool is usually a streaming JSON differ in Node (`@graphql-tools/json-canonicalize` plus a custom walker, or `dyff` for YAML/JSON workflows) or a domain-specific diff like `git diff` on canonicalised exports. This tool is built for the everyday "paste two API responses or two configs into a workspace" case.
Will this tool stay free?
The basic workflow is designed to stay free. Paid upgrades later will focus on bigger limits, batch work, OCR, saved presets, and ad-free use.