How to Compare Two JSON Objects in JavaScript and Python
JSON.stringify(a) === JSON.stringify(b) fails as soon as key order differs. Here is how to check equality properly in JavaScript and Python, and how to list exactly what changed.
To compare two JSON objects in code, compare the parsed values, never the text. The JSON Compare tool shows the differences between two documents in the browser, and its page explains why a text diff is the wrong way to compare JSON. When the comparison has to run in code, in a test, a sync job or a cache check, you need the same idea as a function.
This guide covers equality checks in JavaScript and Python, a short function that lists every difference by path, and the two details that trip people up: array order and numbers.
Why comparing JSON.stringify output fails
const a = { id: 1, tags: ["x", "y"], total: 10 };
const b = { total: 10, id: 1, tags: ["x", "y"] };
JSON.stringify(a) === JSON.stringify(b); // falseThe objects hold the same data, but their keys were added in a different order, so the strings differ. Any data that passed through a database, an API or a merge step can come back with keys in a new order. Compare parsed values instead.
Compare JSON objects in JavaScript and Python
In Node, use the built-in isDeepStrictEqual. It ignores key order and checks everything else:
import { isDeepStrictEqual } from "node:util";
isDeepStrictEqual(a, b); // trueBrowsers have no built-in equivalent. For values that came from JSON.parse, which can only be objects, arrays, strings, numbers, booleans and null, a short function is enough:
function deepEqual(a, b) {
if (a === b) return true;
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
if (Array.isArray(a) !== Array.isArray(b)) return false;
const keysA = Object.keys(a);
if (keysA.length !== Object.keys(b).length) return false;
return keysA.every((key) => Object.hasOwn(b, key) && deepEqual(a[key], b[key]));
}In Python, == on parsed JSON already does the right thing. Dictionaries compare without regard to key order, and lists compare in order:
{"id": 1, "tags": ["x", "y"]} == {"tags": ["x", "y"], "id": 1} # True
{"tags": ["x", "y"]} == {"tags": ["y", "x"]} # FalseList the differences between two JSON objects
A yes or no answer is enough for a cache check. For a log message or a test failure, you want the differences themselves. This function walks both documents and records each one by path:
const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
function diff(left, right, path = "$", out = []) {
if (isPlainObject(left) && isPlainObject(right)) {
for (const key of new Set([...Object.keys(left), ...Object.keys(right)])) {
if (!(key in right)) out.push("removed " + path + "." + key);
else if (!(key in left)) out.push("added " + path + "." + key);
else diff(left[key], right[key], path + "." + key, out);
}
} else if (Array.isArray(left) && Array.isArray(right)) {
for (let i = 0; i < Math.max(left.length, right.length); i++) {
if (i >= right.length) out.push("removed " + path + "[" + i + "]");
else if (i >= left.length) out.push("added " + path + "[" + i + "]");
else diff(left[i], right[i], path + "[" + i + "]", out);
}
} else if (!isDeepStrictEqual(left, right)) {
out.push("changed " + path + ": " + JSON.stringify(left) + " -> " + JSON.stringify(right));
}
return out;
}Two versions of an order
Input
before: { id: 1, status: "paid", items: [{ qty: 2 }], note: "x" }
after: { id: 1, status: "shipped", items: [{ qty: 3 }, { qty: 1 }], gift: true }Output
changed $.status: "paid" -> "shipped"
changed $.items[0].qty: 2 -> 3
added $.items[1]
removed $.note
added $.giftIn a browser, replace isDeepStrictEqual with the deepEqual function above.
When array order does not matter
Every check above treats arrays as ordered, which is what JSON means. Some arrays are really sets, such as tags or the items of an order returned in database order. Sort both sides by a stable key before comparing:
const bySku = (x, y) => x.sku.localeCompare(y.sku);
isDeepStrictEqual([...before.items].sort(bySku), [...after.items].sort(bySku));by_sku = lambda item: item["sku"]
sorted(before["items"], key=by_sku) == sorted(after["items"], key=by_sku)Sort copies, as shown, so the original arrays keep their order for anything else that uses them.
Numbers
0.1 + 0.2is0.30000000000000004, so a computed total never equals0.3exactly. Compare calculated numbers with a tolerance, for exampleMath.abs(x - y) < 1e-9, ormath.isclosein Python.- JSON does not distinguish
1from1.0. JavaScript parses both to the same number, and Python parses them tointandfloat, which still compare equal with==. A text comparison sees them as different. - Integers above 9007199254740991 lose precision in JavaScript, so two different large IDs can parse to the same number and compare as equal.
Related tools
Deep-merge two or more JSON objects into one.
Recursively sort object keys alphabetically for stable, diffable JSON.
Pretty-print JSON with configurable indentation and instant validation.
Analyze JSON size, depth, key counts, and type distribution.
Related guides
The built-in ways to combine objects lose nested settings. Here is a short, tested deep merge for JavaScript and Python, and how to handle arrays, null and __proto__.
The same data can be written with its keys in a different order in each language, and sometimes on each run. Here is where the order comes from, and the fix for each case.
Two formatters can disagree about the same JSON file. Here is how a team picks one, applies it in every editor and before every commit, and makes CI enforce it.
A payload can be 19,847 characters, 20,197 bytes and 1,803 bytes on the wire, all at once. Here is which number an API limit, a database or a browser actually checks.