DEV Community

hammadrasool2000-svg
hammadrasool2000-svg

Posted on AI-assisted

How to Compare Two JSON Files and Actually Spot the Differences

You have two JSON files: yesterday's API response and today's, or the staging config and the production config. Something is different and you need to find it. You run diff a.json b.json and get a single changed line 4,000 characters long. Not much help.

The problem isn't the diff tool. JSON is often minified, keys come back in a different order, and indentation varies. A text diff compares characters, not data. The fix is to normalize both files first, then diff them.

Here are three ways to do that, from quickest to most thorough.

Why raw JSON diffs are noisy

Take these two payloads:

{"id":42,"name":"Sara","roles":["admin","editor"],"active":true}
Enter fullscreen mode Exit fullscreen mode
{"name":"Sara","id":42,"active":false,"roles":["admin","editor"]}
Enter fullscreen mode Exit fullscreen mode

Only one value changed (active). But because the keys are in a different order and everything sits on one line, a line-based diff reports the whole thing as changed. Three things cause this noise:

  1. Minification: everything is on one line, so any change marks the entire line as changed.
  2. Key order: {"a":1,"b":2} and {"b":2,"a":1} hold the same data but are different text.
  3. Whitespace and indentation: 2 spaces vs 4 spaces vs tabs.

Option 1: Pretty-print and diff in the browser (fastest)

If you just want to eyeball two payloads, paste them into a JSON-aware diff. The JSON Diff Checker on ToolStash pretty-prints both sides before comparing, putting one key on each line, so only the lines that actually changed get highlighted. You can switch between line-by-line and word-by-word views. It runs locally in the browser, so you're not uploading production data anywhere.

Two things to know:

  • If one side isn't valid JSON, it falls back to a plain text diff. Run the input through a JSON formatter first if you suspect a syntax error. It shows the parser's error message, so you can see exactly where it breaks.
  • The comparison is text-based after formatting, so key order still matters. If the two files order keys differently, sort them first (see Option 2).

Option 2: Normalize with jq, then use any diff tool

jq can sort keys and pretty-print in one step. The -S flag sorts object keys recursively:

jq -S . before.json > before.sorted.json
jq -S . after.json  > after.sorted.json
diff -u before.sorted.json after.sorted.json
Enter fullscreen mode Exit fullscreen mode

Output for the example above:

--- before.sorted.json
+++ after.sorted.json
@@ -1,5 +1,5 @@
 {
-  "active": true,
+  "active": false,
   "id": 42,
   "name": "Sara",
   "roles": [
Enter fullscreen mode Exit fullscreen mode

Now the diff shows exactly one change. If you'd rather skip the temp files, use process substitution:

diff -u <(jq -S . before.json) <(jq -S . after.json)
Enter fullscreen mode Exit fullscreen mode

Or, if you use git:

git diff --no-index <(jq -S . before.json) <(jq -S . after.json)
Enter fullscreen mode Exit fullscreen mode

Arrays are not sorted by -S, and that's deliberate, because array order is usually meaningful. If your array order doesn't matter (for example, a list of tags), sort it explicitly:

jq -S '.roles |= sort' before.json
Enter fullscreen mode Exit fullscreen mode

Option 3: A structural diff in code

When you need the differences as data, for a test assertion or a CI check, compare the parsed objects rather than text. Here is a small recursive diff in JavaScript with no dependencies:

function jsonDiff(a, b, path = "") {
  const changes = [];
  if (typeof a !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
    changes.push({ path, before: a, after: b });
  } else if (a && typeof a === "object") {
    const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
    for (const key of keys) {
      const p = Array.isArray(a) ? `${path}[${key}]` : `${path}.${key}`;
      if (!(key in a)) changes.push({ path: p, added: b[key] });
      else if (!(key in b)) changes.push({ path: p, removed: a[key] });
      else changes.push(...jsonDiff(a[key], b[key], p));
    }
  } else if (a !== b) {
    changes.push({ path, before: a, after: b });
  }
  return changes;
}

const before = JSON.parse(require("fs").readFileSync("before.json", "utf8"));
const after  = JSON.parse(require("fs").readFileSync("after.json", "utf8"));
console.log(jsonDiff(before, after));
// [ { path: '.active', before: true, after: false } ]
Enter fullscreen mode Exit fullscreen mode

Because it walks the parsed objects, key order doesn't matter at all. You get a clean list of paths like .user.address.city or .items[3].price, which is easy to log or fail a test on.

In Python, the standard library gets you most of the way for a quick check:

import json
a = json.load(open("before.json"))
b = json.load(open("after.json"))
print(a == b)  # dict comparison ignores key order
print(json.dumps(a, sort_keys=True, indent=2))  # normalized, ready to diff
Enter fullscreen mode Exit fullscreen mode

For larger projects, libraries like deepdiff (Python) or jsondiffpatch (JS) give you richer output, including array moves.

A quick checklist

  • Is it valid JSON? Validate before you diff, or you'll be comparing error noise.
  • Pretty-print both sides with the same indentation.
  • Sort keys if they can come back in any order (jq -S, sort_keys=True).
  • Decide whether array order matters. Sort arrays only when it doesn't.
  • Ignore volatile fields like timestamps or request IDs: jq 'del(.meta.requestId)'.
  • Use a structural diff in tests, and a visual diff for debugging.

Normalize first, diff second. Once you do that, a JSON diff shows only the fields that actually changed.

Top comments (0)