How to Compare Two JSON Files (and Read the Diff Correctly)
Why stringify-equality lies, the gotchas that fool naive diffs (key order, array order, types), and how to read a semantic diff.
A test of mine once failed because two API responses "didn't match" -- except they did. The data was identical; the server had just serialized the keys in a different order. My assertion was JSON.stringify(actual) === JSON.stringify(expected), and that check is wrong more often than people realize. If you compare JSON -- for regression tests, config drift, or reviewing an API change -- here's how to do it correctly.
Why JSON.stringify(a) === JSON.stringify(b) lies
JSON objects are unordered by specification -- {"a":1,"b":2} and {"b":2,"a":1} represent the same data. But JSON.stringify preserves insertion order, so the two produce different strings and a string comparison reports a difference that isn't real:
// Order-sensitive -- falsely reports a difference
JSON.stringify({a: 1, b: 2}) === JSON.stringify({b: 2, a: 1}); // false
// Canonicalize keys recursively, THEN compare
function canonical(v) {
if (Array.isArray(v)) return v.map(canonical);
if (v && typeof v === "object") {
return Object.keys(v).sort().reduce((o, k) => (o[k] = canonical(v[k]), o), {});
}
return v;
}
const equal = (a, b) => JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
This sorts keys at every level so object reordering no longer counts as a change -- while keeping array order significant, which it should be.
The gotchas a naive diff trips on
Key order: objects are unordered; reordering is not a real change (but string equality says it is).
Array order: arrays are ordered --
[1, 2]≠[2, 1]. Only sort them if you genuinely mean "set," not "list."Type coercion:
1(number) and"1"(string) are different in JSON. A loose compare that coerces them will miss a real change.null vs missing: a key set to
nullis not the same as a key that's absent.Number normalization:
1vs1.0, or float precision, can read as changes depending on the serializer.
Textual diff vs semantic diff
The single biggest mistake is using a line-by-line diff (like git diff) on JSON. Reformat or reorder the file and the entire thing lights up red. You want a semantic diff that compares by key path.
Comparing JSON correctly in code
In Python, sort keys for a canonical compare, or use DeepDiff for a real field-by-field report:
import json
# Naive string compare is order-sensitive too:
json.dumps({"a": 1, "b": 2}) == json.dumps({"b": 2, "a": 1}) # False
# Canonical compare -- sort keys at every level:
def equal(a, b):
return json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True)
# A real, path-level diff:
from deepdiff import DeepDiff
DeepDiff(old, new) # -> {'values_changed': {...}, 'dictionary_item_added': [...]}
A faster way: a visual semantic diff (no script)
When I just need to see what changed between two payloads -- in a code review or while debugging a failing test -- a visual diff beats writing a comparison script. Our free JSON Compare tool does a side-by-side semantic diff: it highlights added, removed, and changed values by path, ignores cosmetic reordering, and runs entirely in your browser (disclosure: I built it). For applying a known set of changes rather than just viewing them, JSON Patch (RFC 6902) is the structured counterpart.
FAQs
**Does key order matter when comparing JSON?**By spec, object keys are unordered, so reordering isn't a real change -- but JSON.stringify equality treats it as one. Use a canonical compare (sort keys first) or a semantic diff.
**Is [1, 2] the same as [2, 1]?**No -- array order is significant in JSON. Only sort both sides first if you're treating the array as an unordered set.
**Why does my diff show everything as changed?**Almost always reformatting or key reordering. Switch from a line-by-line diff to a semantic/structural one that compares by key path.
**How do I compare two large JSON files?**Use a semantic diff that reports changes by path (a tool, or DeepDiff in Python) rather than scrolling a line diff. Validate both files parse first.
