🔍 JSON Diff (Structural Compare)
Compare two JSON documents structurally and show additions, deletions, and changes with JSON Pointer paths (/path/to/field). Supports key sort, array order ignore, and type-change detection.
🔒 About Privacy
- ・All processing happens entirely in your browser
- ・Your input data is never sent to any server
Diff Result
🔗 Related Tools
📖 Where people get stuck
Compares two JSON documents as structures and reports additions, removals and changes with a JSON Pointer for each. Everything runs in the browser. It looks at something different from a text diff: differences in indentation or line breaks are not differences here. Conversely, two documents identical character for character can still differ structurally, and the reverse happens too — misread that boundary and you will misread the result.
| Case | What happens | What to do |
|---|---|---|
| Inserting one element at the front marks every element changed | Arrays are matched position against position. Insert one element at the front and everything after it shifts by one, producing an unreadable diff that says /0 changed, /1 changed, and one appended at the end. A text diff can say inserted one line because it computes a longest common subsequence; structural comparison does not. With a few dozen elements the whole diff goes red and becomes effectively unreadable. | Switch array order to ignore; the arrays are then compared as sets and a pure reordering reads as unchanged. That is not available to you when the order carries meaning — steps, priorities, rankings. In practice the most effective move is sorting both sides by id before comparing: API responses often make no ordering guarantee, and being shown that jitter as a diff tells you nothing useful. As a rule for reading diffs: when every entry is marked changed, suspect a failed alignment before you believe everything really changed. |
| Numbers that look identical are flagged, or real changes are not | JSON does not distinguish integers from floating-point numbers. 1 and 1.0 parse to the same value and produce no difference, whereas the string "1" and the number 1 are distinct under strict type change. API responses flipping a field between string and number are not unusual — encoding large integers as strings to carry them safely is a common design. The dangerous case is the opposite one: a JavaScript Number cannot represent integers above two to the fifty-third exactly, so 9007199254740993 and 9007199254740992 become the same value at parse time and no difference is reported at all. |
If all you want is to tolerate the type jitter, switch type change to loose. The large-integer problem, however, affects every comparison that passes through JavaScript, not just this tool. If the data holds Snowflake ids, snapshot version numbers or money as integer minor units, you need to treat those fields as strings before comparing — pass a reviver to the parser, or exclude them and check them separately. Spotting it is easy: be suspicious of any value longer than sixteen digits, since two to the fifty-third is 9,007,199,254,740,992, which has sixteen. |
| null and a missing key keep tripping you up | {"a": null} and {} are structurally different, so a difference is reported. That is correct behaviour, but real systems often do not distinguish the two, and you end up with a flood of meaningless entries. The cause sits in the languages: JavaScript JSON.stringify drops keys whose value is undefined, while Python json.dumps emits None as null. Expressing the same absent value, one side removes the key entirely and the other leaves a null behind. Comparing APIs across languages, this is where the diff reliably balloons. |
Normalise to one convention before comparing. In practice, stripping keys whose value is null from both sides is the easiest — it equates absent with null, so only do it where that assumption holds. Never normalise when an API distinguishes the two deliberately. In PATCH semantics, {"a": null} means delete a, while omitting the key means leave a alone (JSON Merge Patch, RFC 7386). Flatten that distinction and an update request generated from your diff will delete fields you never touched. Whether normalising is safe is always decided by the specification of the API at the other end. |
JSON Pointer (RFC 6901) has escaping rules: a ~ inside a key is written ~0 and a / is written ~1. Any dictionary keyed by URLs or file paths runs into this — the pointer to the key in {"https://example.com/a": 1} is /https:~1~1example.com~1a. Pasting a displayed pointer straight into code and forgetting to unescape it lands you on a different path entirely. One more point about order: key order in a JSON object carries no meaning per the specification, yet most parsers preserve insertion order. A key-order difference is therefore information that the meaning is unchanged but the producer is not, and it is a useful clue that a library was upgraded or a serialiser swapped — worth understanding once before you dismiss it as noise.
📖 How to Use
-
1
Paste two JSON documentsPaste the old version on the left and the new one on the right. The Sample buttons load example data.
-
2
Adjust optionsChoose the view (tree / flat / unified), whether to ignore key order or array order, and how strict type comparison should be.
-
3
Review the diffAdded (+), removed (-), and changed (~) entries appear color-coded with JSON Pointer paths. The summary shows totals at a glance.
❓ Frequently Asked Questions
What is JSON Pointer?
What changes when array order is ignored?
How are string "1" and number 1 compared?
🐛 Found a bug or issue with this tool?
Free to use, no signup. Even just the steps to reproduce are helpful. Reports go directly to the operator and help us fix issues.
Thanks for your report!
Your report has been delivered to the operator and will be used to improve the tool.