The Quick Answer
To compare two JSON objects, use a structural diff — not a text diff. A structural diff parses both documents and compares keys and values recursively, ignoring formatting and key order. This gives you the exact path to each change (e.g., user.address.city), categorized as added, removed, or modified.
You can compare JSON objects online by pasting both documents side by side, or use command-line tools like jq and diff, or libraries in your programming language.
Why Text Diff Doesn't Work Well for JSON
A text diff (like git diff or diff on the command line) compares files line by line. For JSON, this creates false positives:
- Reformatting — switching from minified to pretty-printed JSON shows every line as changed, even though the data is identical
- Key reordering —
{"a":1,"b":2}and{"b":2,"a":1}are the same JSON object, but a text diff sees two different lines - Indentation changes — adding or removing a nesting level shifts all child lines
A structural JSON diff avoids all of these. It parses both documents into data structures first, then compares values by key path.
How Structural JSON Diffing Works
The algorithm walks both objects recursively:
- Collect all keys from both objects (the union of their key sets).
- For each key, check:
- Present in original only → removed
- Present in modified only → added
- Present in both, same value → unchanged
- Present in both, different value → changed (or recurse if both values are objects)
- For arrays, compare element by element at matching indices. Extra elements at the end are added or removed.
The output is a flat list of changes, each with a dot-notation path and the old/new values.
Example
Original:
{
"name": "Alice",
"age": 28,
"address": {
"city": "Berlin",
"zip": "10115"
},
"tags": ["admin", "editor"]
}
Modified:
{
"name": "Alice",
"age": 29,
"address": {
"city": "Munich",
"zip": "80331",
"country": "DE"
},
"tags": ["admin", "viewer"]
}
Diff result:
age: 28 → 29 (changed)address.city: "Berlin" → "Munich" (changed)address.zip: "10115" → "80331" (changed)address.country: added "DE"tags[1]: "editor" → "viewer" (changed)
The key name and tags[0] are identical, so they don't appear in the diff.
Array Comparison: The Tricky Part
Objects are straightforward to diff because keys act as stable identifiers. Arrays are harder because elements are identified only by their position (index).
Index-based comparison (most common)
Compare element 0 to element 0, element 1 to element 1, and so on. Simple and fast, but inserting an element at the start cascades changes through every subsequent index.
Original: ["a", "b", "c"]
Modified: ["x", "a", "b", "c"]
An index-based diff reports:
[0]: "a" → "x" (changed)[1]: "b" → "a" (changed)[2]: "c" → "b" (changed)[3]: added "c"
That's technically correct by position, but misleading — the real change was one insertion at the beginning.
LCS-based comparison (smarter)
Some tools use the Longest Common Subsequence (LCS) algorithm to detect insertions and deletions rather than treating every shifted element as changed. This produces a more intuitive diff but is computationally more expensive.
Key-based array comparison
For arrays of objects with a unique identifier (like id), the most accurate approach is to match elements by their ID, then diff each matched pair. This handles reordering, insertion, and deletion correctly.
Practical tip: If you're diffing arrays and getting noisy results, sort both arrays by a stable key before comparing.
Formal Standards: JSON Patch and Merge Patch
Two RFCs formalize how to express JSON changes:
JSON Patch (RFC 6902)
A JSON Patch document is an array of operation objects:
[
{ "op": "replace", "path": "/age", "value": 29 },
{ "op": "replace", "path": "/address/city", "value": "Munich" },
{ "op": "add", "path": "/address/country", "value": "DE" },
{ "op": "replace", "path": "/tags/1", "value": "viewer" }
]
Each operation has:
op— the action:add,remove,replace,move,copy, ortestpath— a JSON Pointer (RFC 6901) to the target locationvalue— the new value (for add/replace)
JSON Patch is expressive enough to handle any change, including array insertions and moves. It's commonly used with HTTP PATCH requests.
JSON Merge Patch (RFC 7396)
A simpler approach — send only the changed fields:
{
"age": 29,
"address": {
"city": "Munich",
"zip": "80331",
"country": "DE"
}
}
Fields present in the patch are added or replaced. Fields set to null are removed. Fields absent from the patch are left unchanged.
Limitation: Merge Patch cannot set a value to null (it interprets null as "delete"), and it cannot express array modifications — the entire array must be replaced.
When to Use Which
- JSON Patch — when you need precise control: array modifications, move operations, or conditional updates (test op)
- Merge Patch — when simplicity matters and you're only updating object fields
Common Pitfalls
1. Treating key order as meaningful
JSON objects are unordered. {"a":1,"b":2} equals {"b":2,"a":1}. If your comparison tool shows differences for reordered keys, it's doing text comparison, not structural comparison.
2. Forgetting that null ≠ missing
{"name": null} and {} are different. The first explicitly declares name as null; the second has no name key. APIs sometimes use this distinction to differentiate "set to empty" from "don't touch."
3. Floating-point edge cases
0.1 + 0.2 in JSON might be stored as 0.30000000000000004 in one system and 0.3 in another. Both are valid JSON numbers. If you're comparing financial or scientific data, consider rounding before diffing.
4. Large arrays with small changes
A single insertion at the start of a 1,000-element array makes every element look changed in an index-based diff. Sort arrays by a unique key before comparing, or use a tool that supports LCS-based or key-based matching.
5. String encoding differences
"café" and "caf\u00e9" are the same JSON string. If you're comparing raw bytes, they look different. Always parse JSON before comparing — the parser handles Unicode escapes.
6. Comparing across different serializers
Different languages serialize the same data differently. Python's json.dumps and JavaScript's JSON.stringify may produce different key orders, different number formatting, or different Unicode handling. Structural comparison handles this; text comparison does not.
Comparing JSON from the Command Line
Using jq (recommended)
jq can sort keys, which makes text diff meaningful:
diff <(jq --sort-keys . file1.json) <(jq --sort-keys . file2.json)
This normalizes both files (sorted keys, consistent formatting) before text-diffing.
Using Python
import json, sys
with open('file1.json') as f1, open('file2.json') as f2:
obj1 = json.load(f1)
obj2 = json.load(f2)
print("Identical" if obj1 == obj2 else "Different")
Python's == does deep structural comparison on dicts and lists.
Using Node.js
const fs = require('fs');
const obj1 = JSON.parse(fs.readFileSync('file1.json', 'utf8'));
const obj2 = JSON.parse(fs.readFileSync('file2.json', 'utf8'));
// Deep comparison (simple, no diff output)
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)
? 'Identical' : 'Different');
For actual diff output, use libraries like deep-diff, json-diff, or fast-json-patch.
Choosing a JSON Diff Approach
| Situation | Recommended Approach |
|---|---|
| Quick visual comparison | Online JSON diff tool |
| CI/CD pipeline validation | jq --sort-keys + diff |
| API testing | JSON Patch or deep-equal assertion in your test framework |
| Sending partial updates to an API | JSON Merge Patch (simple) or JSON Patch (precise) |
| Comparing large files with arrays | Key-based or LCS-based library |
| Code review of JSON files | git diff with jq normalization |
FAQ
What is the fastest way to compare two JSON objects?
For a quick check, paste both into an online JSON diff tool. For programmatic use, parse both objects and use your language's deep-equality function (== in Python, assert.deepStrictEqual in Node.js, assertEquals in Java with a JSON library).
Does key order affect JSON comparison?
No. JSON objects are unordered by specification (RFC 8259). Two objects with identical keys and values in different order are semantically equal. Always use structural comparison, which ignores key order.
How do I compare JSON arrays that might be reordered?
Sort both arrays by a stable key (like id) before comparing. If elements don't have unique keys, sort by a consistent representation (e.g., JSON.stringify of each element). This turns order-dependent comparison into order-independent comparison.
What tools exist for JSON diffing in CI/CD pipelines?
Common options: jq + diff (shell), json-diff (npm), deepdiff (Python), JsonUnit (Java), and json_diff (Ruby gem). Most CI runners have jq pre-installed.
Can I generate a JSON Patch from two objects?
Yes. Libraries like fast-json-patch (JavaScript), jsonpatch (Python), and zjsonpatch (Java) can compute a RFC 6902 patch from two objects. The patch can then be applied to transform the original into the modified version.
Is comparing minified JSON different from comparing formatted JSON?
Not if you use structural comparison. The parser produces the same data structure regardless of whitespace. If you're using text diff, normalize formatting first (e.g., jq . or JSON.stringify(obj, null, 2)).