JSON Compare

Nothing you enter here is stored, logged or shared.

Paste two JSON documents to see every added, removed and changed value listed by path. Key order is ignored, so only real differences are reported.

How to use

  1. Paste the original JSON on the left.
  2. Paste the JSON you want to compare on the right.
  3. Differences appear below, each with its full path.

How the comparison works

Both documents are parsed and then walked in parallel from the root, comparing whatever is found at the same path on each side. Objects are matched by key, so reordering keys changes nothing; arrays are matched by index. Every difference is reported as one of three kinds: added, meaning the path exists only on the right; removed, meaning it exists only on the left; and changed, meaning both sides have a value at that path but the values differ.

A change in type counts as a change, and it is worth looking at closely when it appears. A field that was the number 42 and is now the string "42" will usually still work through a lenient client and then fail in something downstream that does arithmetic on it. The same goes for a field that was an object and is now an array of one object, which is a common accident in code that builds responses conditionally.

Why arrays are compared by index

This is the design decision that most affects what you see, so it is worth stating plainly. Insert one element at the front of a 200-element array and an index-based comparison reports 200 changes, because every element after the insertion point is now at a different index. Nothing is wrong; the diff is simply answering a different question from the one you had in mind.

The alternative is to search for a longest common subsequence and report the insertion, which is what a line diff does with text. It is not obviously better for JSON. It requires deciding when two objects in a list are the same object, which needs a key you have not specified and the tool cannot guess, and where it guesses wrong it produces a confident and misleading answer. Index comparison is blunt but it never invents a correspondence that is not there.

In practice, when a list has genuinely been reordered, sort both sides by their identifier before comparing.

Null, missing, and reading a path

A key that is present with the value null on one side and absent entirely on the other is reported as added or removed rather than as a change, because those are different states and code treats them differently. Absent usually means the field was never set; null usually means it was set to nothing on purpose. APIs that use one to mean the other cause bugs, and this is the tool that shows you which one you are actually sending.

Paths read as a route from the root: user.roles[2] is the third element of the roles array inside the user object. That syntax is deliberately the same one you would type in JavaScript to reach the value, and close enough to a JSONPath expression to paste into most tools that accept one.

A regression workflow that finds real changes

The common use for a JSON diff is confirming that a deployment changed nothing it should not have. Capture the response from the current version, capture it again from the candidate, and compare. The difficulty is never the comparison itself; it is the noise, because a response usually contains fields that legitimately differ every time.

Strip or normalise those before comparing: generated identifiers, timestamps, request ids, signed URLs with an expiry, and anything reporting duration. A short script that walks the document and replaces such fields with a fixed placeholder turns a diff of two hundred differences into a diff of the one that matters.

Do the same for ordering where the API does not promise it. If a list of tags comes back in a different order each call, sort both sides by a stable key first; otherwise an index-based comparison reports every element as changed and hides the single genuine difference among them.

Equal, identical and equivalent

Two documents can be equal in several different senses, and a tool has to pick one. Byte equality is the strictest and least useful, since reformatting breaks it. Structural equality - the same keys with the same values, regardless of whitespace or key order - is what this comparison performs. Semantic equivalence is a third thing that depends on the schema.

Numbers are where structural comparison gets awkward. JSON does not distinguish integers from floats, so 1, 1.0 and 1e0 are the same value to a parser, and whether a producer emits one or the other can change between library versions without anything meaningful having changed. Trailing zeros disappear on a round trip for the same reason.

Strings can differ in bytes while looking identical, because Unicode allows more than one encoding of the same character: an accented letter can be a single code point or a base letter plus a combining mark. Normalising to NFC before comparing removes that class of false difference, and is worth doing whenever text arrives from more than one source.

When a diff is the wrong instrument

Comparing two examples tells you about those two examples. Checking that responses conform to a rule is a different job, and the tool for it is a schema: JSON Schema for a document, OpenAPI for an endpoint. A schema states that id is an integer and email matches a pattern, and it holds for every response rather than for the pair you happened to capture.

The two work together in practice. Schema validation in continuous integration catches structural drift - a field that changed type, a required property that vanished - while a diff answers the narrower question of what changed between these two specific payloads during an investigation.

Contract testing takes it further by having the consumer publish what it depends on, so the producer's pipeline fails when a change would break a real client rather than when it merely differs from an old sample.

Reading a path and jumping to it

The paths reported here read as a route from the root: user.roles[2] is the third element of the roles array inside the user object. That is deliberately the expression you would type in JavaScript to reach the value, so a path can be pasted into a console with the document in a variable.

The same shape is close to JSONPath, the query language most log and API tools accept, where the expression starts with a dollar sign: $.user.roles[2]. jq, the command-line processor, uses a similar notation without the dollar, so .user.roles[2] extracts the same value from a file or a stream.

For very large documents, that path is the useful output rather than the diff itself. Once you know where a difference lives, jq or a filter in your HTTP client can pull just that subtree from both sides, which is faster to read than any full-document view.

Comparing documents that are too large to read

A multi-megabyte response is common in analytics exports and bulk endpoints, and comparing it whole produces a result nobody can use. The productive approach is to reduce before comparing: select the subtree you care about, project only the fields under discussion, and sort collections by their identifier.

Counting is a useful first pass. If one side has 4,812 records and the other has 4,811, the interesting question is which record is missing, and that is a set comparison of identifiers rather than a document diff - two sorted lists of ids and a line-based diff answer it in seconds.

When the payloads are genuinely large and the comparison has to run repeatedly, a command-line pipeline beats a browser: jq to normalise both files into sorted, compact lines, then diff. Everything runs locally either way, but the command line has no page to keep responsive.

Frequently asked questions

Does key order affect the comparison?
No. Objects are compared by key, not by position, so reordering keys produces no differences. Array elements are compared by index, because order is meaningful in arrays.
Are both documents uploaded anywhere?
No. Both sides are parsed and compared in the page itself, which is what makes it reasonable to paste two real API responses side by side, including whatever customer data they happen to contain.
What does a path like user.roles[2] mean?
It locates the difference inside the document: the third element of the roles array, inside the user object. Dots separate object keys and brackets index arrays.
How is a null value different from a missing key?
They are reported differently. A key present with value null on one side and absent on the other is shown as added or removed, not as a change.

Last updated:

Report a problem

Include the steps, expected result, and actual result. Remove secrets and personal data.

Related tools

Related articles