DEV Community

Cover image for How to Compare Two JSON Objects and Spot the Differences Instantly
Tahmid
Tahmid

Posted on

How to Compare Two JSON Objects and Spot the Differences Instantly

You're two hours into debugging a production issue. Your API contract says the response should look like this:

{
  "user": {
    "id": 42,
    "name": "Alice",
    "role": "admin",
    "active": true
  },
  "permissions": ["read", "write", "delete"]
}
Enter fullscreen mode Exit fullscreen mode

But something downstream is breaking. You log the actual response and squint at it. It looks the same. Roughly. But your authorization middleware just denied an admin user.

That's when you notice: "role" is missing. Or it became "roles". Or "active" is now the string "true" instead of a boolean. Bugs like this are trivially small and brutally hard to spot in a wall of JSON text — especially when the payload is minified or poorly indented.

Here's a practical workflow for comparing two JSON objects and catching differences before they catch you.

Step 1: Format both objects identically

Comparing minified JSON visually is a waste of time. If your two blobs have different indentation, trailing whitespace, or inconsistent key ordering, even a side-by-side diff tool will light up with false positives.

Before anything else, run both objects through a formatter. Paste each one into JSON Indenter and make sure you're comparing apples to apples: same indentation style, same structure, same readability.

Before (minified):

{"user":{"id":42,"name":"Alice","role":"admin","active":true},"permissions":["read","write","delete"]}
Enter fullscreen mode Exit fullscreen mode

After (formatted):

{
  "user": {
    "id": 42,
    "name": "Alice",
    "role": "admin",
    "active": true
  },
  "permissions": ["read", "write", "delete"]
}
Enter fullscreen mode Exit fullscreen mode

Now you can actually read it. This single step eliminates a whole class of "differences" that are really just formatting noise.

Step 2: Validate before you compare

If either object has a syntax error, your diff will give you nonsense. A stray trailing comma, an unquoted key, or a missing bracket will corrupt the comparison before it starts.

Run both objects through the JSON Validator before proceeding. It surfaces syntax errors with exact line numbers, so you fix the structure first and compare content second.

This matters more than you'd think. Many JSON objects in the wild come from sources that produce technically invalid JSON — config files with comments, legacy APIs with trailing commas, log entries with prepended timestamps. Validate early.

Step 3: Compare programmatically when you need precision

For automated tests or CI pipelines, visual comparison doesn't scale. Here's where programmatic diffing becomes essential.

The naive approach — and why it fails:

const a = { user: { id: 42, name: "Alice" } };
const b = { user: { name: "Alice", id: 42 } };

console.log(JSON.stringify(a) === JSON.stringify(b));
// false — key order matters for string comparison, not for JSON semantics
Enter fullscreen mode Exit fullscreen mode

JSON.stringify is order-sensitive. Two objects that are semantically identical can produce different strings if their keys were inserted in a different order.

A better approach — recursive diff with specific output:

function jsonDiff(a, b, path = "") {
  const diffs = [];
  const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]);

  for (const key of allKeys) {
    const fullPath = path ? `${path}.${key}` : key;
    if (!(key in a)) {
      diffs.push(`ADDED: ${fullPath} = ${JSON.stringify(b[key])}`);
    } else if (!(key in b)) {
      diffs.push(`REMOVED: ${fullPath}`);
    } else if (typeof a[key] === "object" && a[key] !== null
               && typeof b[key] === "object" && b[key] !== null) {
      diffs.push(...jsonDiff(a[key], b[key], fullPath));
    } else if (a[key] !== b[key]) {
      diffs.push(`CHANGED: ${fullPath}: ${JSON.stringify(a[key])}${JSON.stringify(b[key])}`);
    }
  }

  return diffs;
}

const result = jsonDiff(
  { user: { id: 42, name: "Alice", role: "admin" }, active: true },
  { user: { id: 42, name: "Alice" }, active: "true" }
);

console.log(result);
// ["REMOVED: user.role", "CHANGED: active: true → \"true\""]
Enter fullscreen mode Exit fullscreen mode

This is far more useful than a raw boolean — you get the exact path and the exact values that changed.

Where types silently bite you

The most dangerous JSON diffs aren't missing keys — they're type changes. A value going from true (boolean) to "true" (string) looks identical in most log outputs but behaves completely differently in application logic.

Your comparison logic needs to be explicit about types. In the function above, a[key] !== b[key] catches this because JavaScript's strict equality distinguishes true from "true". In Python, watch out for integers and floats comparing as equal (1 == 1.0), which can mask precision changes that matter to downstream typed systems.

For a deeper look at the syntax issues and type mismatches that show up most often in real payloads, the 5 Common JSON Errors post on JSON Indenter's blog is worth a read — it covers the patterns that trip up developers most reliably.

When to reach for a library

For production use, libraries like deep-diff (Node.js) or deepdiff (Python) go further: they handle arrays intelligently, track moved elements, and give you structured change records you can iterate over. But for day-to-day debugging — the "why is this response different from yesterday?" question you face a dozen times a month — formatting both payloads consistently, validating them, and running a simple recursive diff gets you to the answer in under two minutes.

What's your go-to approach when two API responses look identical but aren't? Have you ever been burned by a boolean quietly becoming a string, or a key silently renamed in a schema update? Drop your war stories in the comments — I'd love to hear how others handle this.


Free tools used in this post:

Top comments (0)