Skip to content
LocalOnly
JSON Tools

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.

Updated October 18, 20264 min readTool runs in your browser

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);   // false

The 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);   // true

Browsers 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"]}                     # False

List 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   $.gift

In 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.2 is 0.30000000000000004, so a computed total never equals 0.3 exactly. Compare calculated numbers with a tolerance, for example Math.abs(x - y) < 1e-9, or math.isclose in Python.
  • JSON does not distinguish 1 from 1.0. JavaScript parses both to the same number, and Python parses them to int and float, 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.

Frequently asked questions

Command Palette

Search for a tool or command