Structural comparison, and where paths come from
Both sides are parsed first, then walked in parallel. Object keys are matched by name, so {"a":1,"b":2} and {"b":2,"a":1} report as identical. Arrays are matched by index unless you turn that off. Every difference is reported at the path where it sits: object keys joined with dots, array indices in brackets, so data.users[2].address.zip is the zip of the third user. That notation is deliberately the same shape as the JavaScript expression that reads the value, and close enough to jq (.data.users[2].address.zip) and JSONPath ($.data.users[2].address.zip) to paste into either with a small edit.
A key present on one side only is reported as added or removed. A key present on both with a different value is a change, and if the type changed as well the report says so — number → string next to the values. Type flips are worth looking at first, because they are usually a serialisation change rather than a data change, and they break consumers that a value change would not.
The array-order option, and when it lies to you
By default ["a","b"] against ["b","a"] is two changes, because index 0 and index 1 both hold something different. For a list of tags or permissions that is noise. Turn on the set comparison and elements are matched by content instead: same multiset, no difference.
When the counts match but the contents do not, the leftovers get paired up in the order they were found and compared element by element, so you get a useful inner diff rather than a wholesale replace. That pairing is a guess. For an array of objects with no obvious identity, the guess is frequently wrong and you get a plausible-looking diff of two records that have nothing to do with each other. If the elements have an id, sort both arrays by it first and leave the index comparison on. You will get an answer you can trust instead of one you have to audit.
Reading the counts
The unchanged figure counts leaf values, not keys — a nested object with ten fields contributes ten, not one. The percentage underneath is unchanged leaves over all leaves that were visited, which is a reasonable proxy for "how much of this document moved" when both sides are the same shape, and meaningless when one side is half the size of the other. Read it as a sanity check: a diff that claims two changes out of four hundred is believable, a diff that claims four hundred out of four hundred usually means the two documents are not the same kind of thing.
What it will not catch
Both sides go through JSON.parse, which means anything the parse flattens is invisible here. 1.0 and 1 are the same number. Integers past 253 are rounded on both sides, so two genuinely different IDs can compare equal. Duplicate keys in the source text collapse to the last one before comparison ever starts. Key order and whitespace are gone by design.
What it does catch, reliably, is a whitespace difference inside a string, a case change, and a number that became the string of that number. Those three account for most of the confusing diffs people bring to this kind of tool.
Nothing is uploaded
Both documents are parsed and compared inside the tab. No request is made, nothing is stored, and the copyable text output is generated locally too. Two production API responses with real customer data in them are safe to paste, subject to the obvious caveat that they will be on your screen afterwards.
Questions people ask
The two files look different but you say they match.
Then the difference is in the text, not the data: key order, indentation, line endings, or a number written as 1.0 on one side and 1 on the other. The comparison runs after parsing, so all of those are gone by the time anything is compared. If the byte-level difference is what you care about — say you are debugging a signature or a checksum over the raw body — use a plain text diff instead, because this tool deliberately throws that information away.
How do you tell a null value from a missing key?
They are reported differently. A key that exists on both sides but holds null on one is a change, shown as a value going to or from null. A key that exists on only one side is an addition or a removal. This distinction matters more than it looks: a consumer reading a missing key gets undefined and often falls through to a default, while one reading an explicit null usually does not.
Can I use the output as a JSON Patch?
Not directly. The copyable text uses +, - and ~ prefixes and dotted paths because that is easier to read and to paste into a ticket. RFC 6902 JSON Patch wants an array of operation objects with JSON Pointer paths, where separators are slashes and array indices are bare. The information is all there, so converting is mechanical, but this does not do it for you.
Why did an ordered comparison report every element as changed?
One element was inserted or deleted near the front, which shifts every later index by one. Index-based comparison has no way to recognise that, so it reports the entire tail as modified. This is the classic case for turning on the set comparison, or for sorting both arrays by a stable key before comparing.