Compare JSON in Python
Comparing two JSON structures in Python has 3 levels of depth: the == equality operator (exact match, returns True/False), manual key-by-key comparison (gives details but requires writing code), and the deepdiff library — which returns a structured dictionary of every addition, removal, type change, and value change between two JSON objects. deepdiff.DeepDiff(obj1, obj2) is the standard tool for JSON comparison in CI pipelines, API testing, and data migration verification. This guide covers all 3 methods, including ignore_order=True for unordered array comparison, file-level JSON comparison, and using assertEqual with json.loads() in Python unit tests.
Paste two JSON payloads into Jsonic to see a visual diff instantly.
Open JSON Diff ToolPython == for exact JSON equality
The simplest way to compare two JSON values in Python is to parse JSON in Python with json.loads() first, then use ==. Comparing raw JSON strings is unreliable: whitespace differences and key ordering differences will cause false negatives even when the data is semantically identical. Once you have Python dicts, == performs a deep recursive comparison.
One important asymmetry: Python dicts compare order-independently (because dict equality ignores insertion order), but Python lists compare order-dependently. This means {"a":1,"b":2} == {"b":2,"a":1} is True, but [1, 2] != [2, 1].
import json
# Always parse strings before comparing — raw string comparison is
# whitespace-sensitive and key-order-sensitive.
str1 = '{"b": 2, "a": 1}'
str2 = '{"a": 1, "b": 2}'
# String comparison: False (key order differs)
print(str1 == str2) # False
# Dict comparison: True (dicts are order-independent)
obj1 = json.loads(str1)
obj2 = json.loads(str2)
print(obj1 == obj2) # True
# Arrays ARE order-dependent
arr1 = json.loads('[1, 2, 3]')
arr2 = json.loads('[3, 2, 1]')
print(arr1 == arr2) # False ← same elements, different order
# Nested structure — deep comparison works automatically
a = json.loads('{"user": {"name": "Alice", "roles": ["admin", "editor"]}}')
b = json.loads('{"user": {"name": "Alice", "roles": ["admin", "editor"]}}')
print(a == b) # True
# Limitation: == only tells you True/False, not what is different
c = json.loads('{"user": {"name": "Bob", "roles": ["viewer"]}}')
print(a == c) # False — but which field changed? == won't tell you.Use == for quick sanity checks, cache invalidation (has this object changed since last time?), and verifying that a serialization round-trip doesn't mutate data. When you need to know what changed, use deepdiff.DeepDiff() instead.
deepdiff.DeepDiff(): structured diff with details
deepdiff is the standard Python library for comparing nested data structures. Install it with pip install deepdiff. The result of DeepDiff(obj1, obj2) is a dict-like object — if it is empty (falsy), the objects are equal; otherwise it contains typed change categories that tell you exactly what changed, where it changed (using a path notation like root['address']['city']), and what the old and new values are.
from deepdiff import DeepDiff
import json
old = json.loads('''{
"id": 101,
"name": "Alice",
"address": {"city": "Paris", "zip": "75001"},
"roles": ["viewer"],
"score": "42"
}''')
new = json.loads('''{
"id": 101,
"name": "Alice",
"address": {"city": "Lyon", "zip": "75001"},
"roles": ["viewer", "editor"],
"score": 42,
"verified": true
}''')
diff = DeepDiff(old, new)
# diff is empty dict ({}) when objects are equal
if not diff:
print("Objects are identical")
else:
print(diff)
# Output (abbreviated):
# {
# 'values_changed': {"root['address']['city']": {'new_value': 'Lyon', 'old_value': 'Paris'}},
# 'type_changes': {"root['score']": {'new_value': 42, 'new_type': int, 'old_value': '42', 'old_type': str}},
# 'iterable_item_added': {"root['roles'][1]": 'editor'},
# 'dictionary_item_added': [root['verified']]
# }
# Export diff as JSON string
print(diff.to_json(indent=2))
# Export diff as plain dict (JSON-serializable)
diff_dict = diff.to_dict()
print(type(diff_dict)) # <class 'dict'>| DeepDiff result key | Meaning | Example path |
|---|---|---|
values_changed | Same key exists in both objects but the value changed | root['address']['city'] |
dictionary_item_added | Key present in obj2 but not in obj1 | root['verified'] |
dictionary_item_removed | Key present in obj1 but missing from obj2 | root['deprecated_field'] |
type_changes | Same key, same path, but the type changed (e.g. string → int) | root['score'] |
iterable_item_added | Element added to a list in obj2 | root['roles'][1] |
iterable_item_removed | Element removed from a list in obj2 | root['tags'][0] |
attribute_added | Attribute added to an object (when comparing class instances) | root.new_attr |
repetition_change | Same element, different repetition count in a list (with ignore_order=True) | root['tags'] |
Ignoring array order with ignore_order=True
A common pain point when comparing API responses is that arrays may be returned in non-deterministic order — a list of tags, permissions, or search results that is logically identical but physically ordered differently on each call. The ignore_order=True parameter tells DeepDiff to use value-based matching instead of index-based matching for lists, so two arrays with the same elements in different positions are treated as equal.
from deepdiff import DeepDiff
import json
# Simulated API responses — same data, different array order
response_a = json.loads('''{
"user": "alice",
"permissions": ["read", "write", "delete"],
"tags": ["python", "api", "json"]
}''')
response_b = json.loads('''{
"user": "alice",
"permissions": ["delete", "read", "write"],
"tags": ["json", "python", "api"]
}''')
# Without ignore_order — reports spurious differences
diff_ordered = DeepDiff(response_a, response_b)
print(diff_ordered)
# {'values_changed': {"root['permissions'][0]": {...}, ...}} ← false positives
# With ignore_order — treats lists as unordered sets
diff_unordered = DeepDiff(response_a, response_b, ignore_order=True)
print(diff_unordered) # {} ← correctly identifies them as equal
# ignore_order works recursively through nested structures
nested_a = {"items": [{"id": 2, "val": "b"}, {"id": 1, "val": "a"}]}
nested_b = {"items": [{"id": 1, "val": "a"}, {"id": 2, "val": "b"}]}
print(DeepDiff(nested_a, nested_b)) # reports differences
print(DeepDiff(nested_a, nested_b, ignore_order=True)) # {}
# significant_digits — for floating point comparisons
obj1 = {"ratio": 0.10000001}
obj2 = {"ratio": 0.10000002}
print(DeepDiff(obj1, obj2)) # reports values_changed
print(DeepDiff(obj1, obj2, significant_digits=5)) # {} ← equal to 5 sig figsNote that ignore_order=True has a performance cost on large arrays because DeepDiff uses a hash-based set approach to match elements. For very large arrays of complex objects, consider sorting both arrays by a stable key before comparing, then using the default ordered comparison.
Compare JSON files on disk
To compare two JSON files, read each one with json.load() (which reads from a file object, unlike json.loads() which takes a string) and then diff the resulting Python dicts. The function below returns a DeepDiff result or None if the files are equal, and handles file-not-found and JSON parse errors explicitly.
import json
from deepdiff import DeepDiff
from pathlib import Path
def compare_json_files(
path_a: str,
path_b: str,
ignore_order: bool = False,
) -> DeepDiff | None:
"""
Compare two JSON files.
Returns a DeepDiff result if they differ, or None if they are equal.
Raises FileNotFoundError or json.JSONDecodeError on bad input.
"""
file_a = Path(path_a)
file_b = Path(path_b)
if not file_a.exists():
raise FileNotFoundError(f"File not found: {path_a}")
if not file_b.exists():
raise FileNotFoundError(f"File not found: {path_b}")
with file_a.open(encoding="utf-8") as f:
obj_a = json.load(f) # raises json.JSONDecodeError if malformed
with file_b.open(encoding="utf-8") as f:
obj_b = json.load(f)
diff = DeepDiff(obj_a, obj_b, ignore_order=ignore_order)
return diff if diff else None
# Usage
result = compare_json_files("config_v1.json", "config_v2.json", ignore_order=True)
if result is None:
print("Files are identical")
else:
print("Files differ:")
print(result.to_json(indent=2))
# Simple version using only == (no deepdiff dependency)
def json_files_equal(path_a: str, path_b: str) -> bool:
"""Returns True if both JSON files contain semantically equal data."""
with open(path_a, encoding="utf-8") as f:
obj_a = json.load(f)
with open(path_b, encoding="utf-8") as f:
obj_b = json.load(f)
return obj_a == obj_b
# Use case: verify a config file didn't change after deployment
if not json_files_equal("expected_config.json", "deployed_config.json"):
raise RuntimeError("Deployed config does not match expected config!")For reading JSON files in general (not just comparison), see the full guide on reading JSON files in Python.
JSON comparison in Python unit tests
Testing JSON-producing code — API endpoints, serializers, data pipelines — requires comparing Python dicts or JSON strings in a way that gives useful failure messages. Both unittest and pytest support this well when you parse JSON before asserting.
unittest: assertEqual and assertDictEqual
import json
import unittest
class TestApiResponse(unittest.TestCase):
def test_user_endpoint(self):
# Simulate an API response body (as if returned by requests.get(...).text)
response_text = '{"id": 1, "name": "Alice", "email": "alice@example.com"}'
expected = {
"id": 1,
"name": "Alice",
"email": "alice@example.com",
}
# assertEqual on dicts: order-independent, shows full diff on failure
self.assertEqual(expected, json.loads(response_text))
# assertDictEqual: same as assertEqual for dicts, but more explicit
self.assertDictEqual(expected, json.loads(response_text))
def test_canonical_string_comparison(self):
# json.dumps with sort_keys=True produces a canonical string
# — useful when you need to compare as strings (e.g. in caches)
obj1 = {"b": 2, "a": 1}
obj2 = {"a": 1, "b": 2}
# Raw string comparison fails due to key order
self.assertNotEqual(json.dumps(obj1), json.dumps(obj2))
# Canonical comparison succeeds
self.assertEqual(
json.dumps(obj1, sort_keys=True),
json.dumps(obj2, sort_keys=True),
)pytest: plain assert with detailed output
import json
import pytest
from deepdiff import DeepDiff
# pytest shows a rich diff when assert fails — no need for assertDictEqual
def test_transform_output():
input_data = {"name": "alice", "score": "99"}
result = {"name": "alice", "score": 99} # score coerced to int
expected = {"name": "alice", "score": 99}
assert result == expected # pytest will show the full dict diff on failure
# Using deepdiff in pytest for nested structures with unordered arrays
def test_api_returns_correct_permissions():
response_json = '{"user": "alice", "permissions": ["write", "read", "delete"]}'
expected = {
"user": "alice",
"permissions": ["read", "write", "delete"], # different order
}
actual = json.loads(response_json)
# Plain == would fail due to array order
# DeepDiff with ignore_order=True correctly identifies them as equal
assert DeepDiff(expected, actual, ignore_order=True) == {}, (
f"Response differed from expected:\n{DeepDiff(expected, actual, ignore_order=True)}"
)
# pytest fixture: compare response against a fixture JSON file
@pytest.fixture
def expected_user():
with open("tests/fixtures/user_alice.json", encoding="utf-8") as f:
return json.load(f)
def test_get_user_matches_fixture(expected_user):
# Replace with your actual API call
response_text = '{"id": 1, "name": "Alice", "email": "alice@example.com"}'
actual = json.loads(response_text)
diff = DeepDiff(expected_user, actual, ignore_order=True)
assert diff == {}, f"API response differed from fixture:\n{diff.to_json(indent=2)}"For understanding how the underlying diff algorithm works, see the guide on how JSON diff works.
Frequently asked questions
How do I compare two JSON files in Python?
Load both files with json.load() and use == for exact equality: with open('a.json') as f: obj1 = json.load(f), then with open('b.json') as f: obj2 = json.load(f), and finally are_equal = obj1 == obj2. For a detailed diff that shows what changed, use deepdiff: from deepdiff import DeepDiff; diff = DeepDiff(obj1, obj2). An empty DeepDiff result {} means the files are identical. Use ignore_order=True if array element order shouldn't matter.
How do I find what changed between two JSON objects in Python?
Use deepdiff.DeepDiff(old, new). The result is a dictionary with typed change categories: values_changed (same key, different value), dictionary_item_added (new key in new), dictionary_item_removed (key missing from new), type_changes (same key, different type), and iterable_item_added / iterable_item_removed (array element changes). Each entry includes the path to the changed field using DeepDiff's path notation: root['address']['city'] means the city field inside address.
Does Python's == operator compare JSON arrays in order?
Yes — Python list comparison is order-sensitive: [1, 2, 3] != [3, 2, 1] even though both contain the same elements. JSON objects (Python dicts) are order-independent: {"a": 1, "b": 2} == {"b": 2, "a": 1} returns True. To compare arrays regardless of order, convert them to sets (if elements are hashable), or use DeepDiff(obj1, obj2, ignore_order=True) for nested structures.
How do I ignore key order when comparing JSON in Python?
Python dicts are already order-independent for equality — {"a":1,"b":2} == {"b":2,"a":1} is True. For arrays where element order shouldn't matter, use DeepDiff(obj1, obj2, ignore_order=True). For string comparison (comparing serialized JSON), use json.dumps(obj, sort_keys=True) — this canonicalizes both objects before comparison: json.dumps(obj1, sort_keys=True) == json.dumps(obj2, sort_keys=True).
How do I use deepdiff with JSON in Python?
Install with pip install deepdiff. Parse your JSON strings with json.loads(), then pass the resulting Python dicts to DeepDiff: from deepdiff import DeepDiff; import json; obj1 = json.loads(str1); obj2 = json.loads(str2); diff = DeepDiff(obj1, obj2, ignore_order=True). If diff is empty ({}), the objects are equal. Otherwise, iterate over diff.items() to see each change type and its value. Call diff.to_json() to get a JSON-serializable summary of changes.
How do I compare JSON in Python unit tests?
For unittest, use self.assertEqual(expected_dict, json.loads(response.text)) — dict comparison is order-independent and produces a clear failure message showing the actual vs expected structure. For pytest, use assert expected_dict == json.loads(response.text) — pytest will show a detailed diff in the failure output. For complex nested structures, add from deepdiff import DeepDiff and assert DeepDiff(expected, actual, ignore_order=True) == {} — the empty dict means no differences, and if the assertion fails, DeepDiff's output shows exactly what changed.
Compare your JSON visually
Paste two JSON documents into Jsonic's diff tool to see additions, removals, and changes highlighted side-by-side — no installation required.
Open JSON Diff Tool