Two JSON objects that contain the same data — the same keys and the same values — can fail a naive equality check because one has keys in alphabetical order and the other has them in the order they were inserted, and any diff tool that compares raw text will report them as different
The previous articles on this site covered why key order doesn't matter in JSON, practical diff workflows, structural diff fundamentals, JSON Patch, and snapshot testing. This article addresses JSON normalization before diffing — the specific pre-processing steps that make structural comparison meaningful rather than noise-generating.
The three normalization problems that corrupt diffs
Before two JSON documents can be meaningfully compared, they often need to be normalized — transformed into a canonical form that eliminates irrelevant differences. Three categories of "false differences" appear most frequently:
1. Key ordering: JSON objects are unordered by definition (the spec doesn't guarantee order), but serializers typically produce keys in a specific order — insertion order (JavaScript, Python 3.7+), alphabetical order (some libraries), or serialization-order (whatever the code produced them in). The same logical object serialized by two different code paths may have keys in different orders.
2. Formatting: a "minified" JSON string and a "pretty-printed" equivalent are semantically identical. Any text-level diff will report every line as changed.
3. Numeric representation: 1.0 and 1 and 1.00 are the same number in JSON (all represent the integer 1 with possible decimal places). Some serializers produce 1.0; others produce 1. A text diff reports a difference; a structural diff sees the same value.
Canonical JSON: a formal approach to normalization
RFC 8785 defines "JSON Canonicalization Scheme" (JCS) — a specification for producing a canonical representation of JSON where:
- Keys are sorted lexicographically (Unicode code point order)
- Whitespace is removed (minified)
- Numbers are represented in a specific way (no trailing zeros, no unnecessary decimals)
- Unicode characters are represented consistently
Producing canonical JSON before diffing ensures that two JSON documents representing the same data produce identical strings — making text-level diff tools work correctly on them. Two structurally-identical JSON documents, canonicalized, become byte-identical strings.
When canonical form matters beyond diffing: digital signatures over JSON data require canonical form — if two parties sign the "same" JSON document but with different key orders, their signatures will differ. JCS enables consistent signing of JSON.
Practical normalization workflow
For most development contexts, the normalization steps are simpler than JCS:
- Parse: convert the JSON string to a native data structure (object, dict, map)
- Sort keys recursively: sort all object keys alphabetically at all nesting levels
- Serialize: convert back to JSON with consistent formatting (usually minified, or pretty-printed with consistent indentation)
- Diff the normalized outputs
In JavaScript:
function canonicalize(obj) {
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
return obj;
}
return Object.fromEntries(
Object.keys(obj).sort().map(k => [k, canonicalize(obj[k])])
);
}
const normalized1 = JSON.stringify(canonicalize(JSON.parse(json1)), null, 2);
const normalized2 = JSON.stringify(canonicalize(JSON.parse(json2)), null, 2);
// Now diff normalized1 vs normalized2
In Python:
import json
def normalize(obj):
if isinstance(obj, dict):
return {k: normalize(v) for k, v in sorted(obj.items())}
elif isinstance(obj, list):
return [normalize(i) for i in obj]
return obj
normalized1 = json.dumps(normalize(json.loads(json1)), indent=2)
normalized2 = json.dumps(normalize(json.loads(json2)), indent=2)
Arrays: the normalization problem that's harder to solve
Key sorting handles the object ordering problem. Arrays are different — array element order is semantically significant in JSON. [1, 2, 3] and [3, 2, 1] are different values, not the same value in a different order.
This creates a genuine diffing challenge: if you have two JSON arrays representing "the same set of items" but in different orders (perhaps the list of authorized users was rebuilt and the database returns them in a different order), a structural diff will show every element as changed, even though the actual change was only to ordering.
No universal solution exists — because JSON doesn't distinguish between "ordered sequences where order matters" (where array order changes are real changes) and "unordered sets that happen to be represented as arrays" (where order changes are noise).
Domain-specific solutions: if you know a specific array should be treated as an unordered set, sort its elements before diffing (for arrays of strings/numbers) or sort by a stable key field (for arrays of objects with IDs). This pre-processing is necessarily domain-specific.
The diff-then-act pattern: using normalized diff for automation
A practical automation pattern: run a normalized diff on two versions of a configuration or API response, and if the diff is non-empty, trigger an alert or action.
Example: a service that fetches configuration from an external API every hour. If the configuration changes, it should restart. Using normalized JSON diff rather than string comparison ensures:
- Reformatted responses don't trigger false restarts
- Reordered keys don't trigger false restarts
- Actual value changes do trigger restarts
def config_changed(old_config, new_config):
old_normalized = json.dumps(normalize(old_config), sort_keys=True)
new_normalized = json.dumps(normalize(new_config), sort_keys=True)
return old_normalized != new_normalized
How to use the JSON Diff tool on sadiqbd.com
- For key-ordering false diffs: the tool's structural comparison already handles key ordering — inserting JSON with differently-ordered keys on both sides should show no differences if the values are the same
- For formatting differences: pasting minified JSON on one side and pretty-printed on the other should show no structural differences (only formatting changes, which the structural diff ignores)
- For array ordering issues: the tool compares arrays positionally by default; if you need set-comparison (order doesn't matter), sort both arrays before pasting
Frequently Asked Questions
Does the order of JSON keys affect performance in any real application?
Rarely in practice, but yes in theory. Some JSON parsers that build hash maps may have slightly different performance characteristics depending on insertion order (depending on hash table implementation). More practically: some poorly-implemented API clients or parsing code may assume a specific key order (accessing response.body.charAt(2) instead of response.body — treating JSON as text), which breaks when the serializer changes key order. The robust approach is always to use a proper JSON parser and access values by key name, making key order entirely irrelevant to correctness.
Is the JSON Diff tool free? Yes — completely free, no sign-up required.
Try the JSON Diff tool free at sadiqbd.com — compare two JSON documents and see only the structural differences, ignoring formatting and key order.