JSON Deep Equal — Testing Structural Equality of JSON Objects
Deep equality checks if two JSON objects have identical structure and values, regardless of object reference. Here's how to implement deep equal for JSON in JavaScript and...
Deep equality checks whether two JSON values have the same structure and data — not whether they’re the same object in memory. This is what you need when validating API responses, comparing config files, or writing tests.
Use the JSON Diff Tool to compare JSON documents and see exactly what differs.
The problem with shallow equality
// Primitive values: reference equality works
42 === 42 // true
'hello' === 'hello' // true
// Objects: reference equality fails
{ a: 1 } === { a: 1 } // false (different references)
[1, 2, 3] === [1, 2, 3] // false
// After JSON roundtrip: always different references
const parsed = JSON.parse('{"a":1}');
parsed === { a: 1 } // false
Deep equality compares values, not references.
What JSON deep equality must handle
// 1. Primitives: value comparison
deepEqual(1, 1) // true
deepEqual('a', 'a') // true
deepEqual(null, null) // true
deepEqual(1, '1') // false (different types)
// 2. Arrays: ordered, element-by-element
deepEqual([1, 2, 3], [1, 2, 3]) // true
deepEqual([1, 2, 3], [1, 3, 2]) // false (order matters)
// 3. Objects: all keys equal, regardless of key order
deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 }) // true
// 4. Nested: recursive
deepEqual({ x: { y: { z: 1 } } }, { x: { y: { z: 1 } } }) // true
// 5. Edge cases
deepEqual(null, undefined) // false
deepEqual(0, -0) // depends on your requirements
deepEqual(NaN, NaN) // NaN !== NaN in JS, but may want true
JavaScript implementation
function deepEqual(a, b) {
// Same reference or same primitive value
if (a === b) return true;
// Null checks
if (a === null || b === null) return a === b;
// Type mismatch
if (typeof a !== typeof b) return false;
// Arrays
if (Array.isArray(a)) {
if (!Array.isArray(b) || a.length !== b.length) return false;
return a.every((item, i) => deepEqual(item, b[i]));
}
// Objects (but not arrays)
if (typeof a === 'object') {
if (Array.isArray(b)) return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every(key => key in b && deepEqual(a[key], b[key]));
}
// Primitives that didn't pass === (e.g., NaN)
return false;
}
// Tests:
deepEqual({ a: 1, b: [1, 2] }, { a: 1, b: [1, 2] }); // true
deepEqual({ a: 1, b: [1, 2] }, { a: 1, b: [2, 1] }); // false
deepEqual(null, null); // true
deepEqual(null, 0); // false
deepEqual({}, {}); // true
deepEqual([], []); // true
deepEqual([], {}); // false
Using libraries in JavaScript
// Option 1: Lodash (battle-tested, handles edge cases like NaN, Date, RegExp)
import { isEqual } from 'lodash';
isEqual({ a: 1 }, { a: 1 }); // true
isEqual([1, 2], [1, 2]); // true
isEqual(NaN, NaN); // true (lodash handles this)
// Option 2: fast-deep-equal (faster, JSON-safe values)
import equal from 'fast-deep-equal';
equal({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }); // true
// Option 3: JSON.stringify (simplest, but key-order sensitive)
JSON.stringify({ a: 1, b: 2 }) === JSON.stringify({ b: 2, a: 1 });
// false — same content, different key order
Python deep equality
Python’s == operator on dicts and lists already does structural comparison:
# Python dicts: structural equality by default
{"a": 1, "b": 2} == {"b": 2, "a": 1} # True — key order doesn't matter
[1, 2, 3] == [1, 2, 3] # True
[1, 2, 3] == [1, 3, 2] # False — list order matters
# Nested:
{"x": {"y": [1, 2]}} == {"x": {"y": [1, 2]}} # True
# Type differences:
1 == 1.0 # True (int and float)
1 == "1" # False
None == None # True
For more control, use deepdiff:
from deepdiff import DeepDiff
a = {"name": "Alice", "scores": [100, 95, 87]}
b = {"name": "Alice", "scores": [100, 95, 87]}
# No differences:
DeepDiff(a, b) == {} # True
# Check with type strictness:
DeepDiff({"n": 1}, {"n": 1.0}, ignore_numeric_type_changes=True)
# {} (treated as equal)
DeepDiff({"n": 1}, {"n": 1.0})
# {'type_changes': {"root['n']": {'old_type': int, 'new_type': float, ...}}}
Deep equal in tests
Jest (JavaScript)
// Jest uses deep equality for toEqual:
expect({ a: 1, b: [2, 3] }).toEqual({ a: 1, b: [2, 3] }); // passes
// vs toBe (reference equality):
const obj = { a: 1 };
expect(obj).toBe(obj); // passes
expect({ a: 1 }).toBe({ a: 1 }); // FAILS — different references
// For API responses:
const response = await fetch('/api/user/1');
const data = await response.json();
expect(data).toEqual({
id: 1,
name: 'Alice',
email: 'alice@example.com',
});
// Partial matching:
expect(data).toMatchObject({ id: 1, name: 'Alice' });
// Passes even if data has extra keys
Pytest (Python)
def test_api_response():
response = client.get('/api/user/1')
data = response.json()
assert data == {
"id": 1,
"name": "Alice",
"email": "alice@example.com",
}
# Partial: check subset
assert data["id"] == 1
assert data["name"] == "Alice"
Order-insensitive array comparison
When array order doesn’t matter (e.g., comparing tag lists):
function deepEqualUnordered(a, b) {
if (!Array.isArray(a) || !Array.isArray(b)) {
return deepEqual(a, b);
}
if (a.length !== b.length) return false;
const sorted_a = [...a].sort();
const sorted_b = [...b].sort();
return deepEqual(sorted_a, sorted_b);
}
deepEqualUnordered(['b', 'a', 'c'], ['c', 'a', 'b']); // true
# Python:
sorted(["b", "a", "c"]) == sorted(["c", "a", "b"]) # True
# With deepdiff:
from deepdiff import DeepDiff
DeepDiff(["b", "a"], ["a", "b"], ignore_order=True) # {}
Related tools
- JSON Diff Tool — compare JSON documents visually
- Compare JSON Objects — finding differences between JSON
- JSON Merge Patch — partial JSON updates
Related posts
- Comparing JSON Structurally (Not Just as Strings) — Two JSON documents can be byte-different and semantically identical. Or byte-ide…
- How to Compare JSON Objects — Deep Equality and Diff in JavaScript and Python — Comparing JSON objects with == won't work for deep equality. Here's how to deep-…
- Detecting Changes in JSON Data — Audit Logs, Diffs, and Change Tracking — Detecting what changed in a JSON document is essential for audit logs, versionin…
- JSON Diff Tool — Compare Two JSON Objects and Find Differences — A JSON diff tool compares two JSON structures semantically, not textually. It fi…
- JSON Merge Patch — Partial Updates to JSON Documents (RFC 7396) — JSON Merge Patch (RFC 7396) is a simple format for partial JSON updates. A null …
Related tool
Compare two JSON objects structurally with field-by-field diff.
Written by Mian Ali Khalid. Part of the Data & Format pillar.