Skip to content

🔍 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.

100% Free No signup Browser-only 5 languages Dark mode

🔒 About Privacy

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. 1
    Paste two JSON documents
    Paste the old version on the left and the new one on the right. The Sample buttons load example data.
  2. 2
    Adjust options
    Choose the view (tree / flat / unified), whether to ignore key order or array order, and how strict type comparison should be.
  3. 3
    Review the diff
    Added (+), 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?
A slash-separated path notation defined in RFC 6901 for pointing at any value inside a JSON document. For example, /users/0/email points to the email field of the first array element.
What changes when array order is ignored?
Arrays are treated as sets — equal contents in different orders are considered unchanged. Useful when only the contents matter, not the position.
How are string "1" and number 1 compared?
In Strict mode they are reported as a change; in Loose mode they are treated as equal by value. Switch to Loose to tolerate type drift in API responses.
🐛 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.

* Browser info (UA / screen / language / URL) is sent automatically to help reproduce the issue