Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 4 min read

JSON Diffing in Practice: Keys, Arrays, Types, and Useful Paths

Comparing two JSON documents by text is often useful right up until it is not. A formatter can make two API responses look different because one serializer reordered object keys or changed indentation. A plain text diff

Comparing two JSON documents by text is often useful right up until it is not.

A formatter can make two API responses look different because one serializer reordered object keys or changed indentation. A plain text diff then produces a wall of noise, even when the values are the same.

The opposite problem is worse. A response can look almost identical while changing a number into a string, replacing null with a missing property, or moving an array item. Those changes can break an API client without producing an obvious visual clue.

A useful JSON diff starts by comparing structure, not presentation.

Object key order is usually noise

Consider these two documents:

{
  "id": "usr_42",
  "role": "editor",
  "active": true
}
{
  "active": true,
  "role": "editor",
  "id": "usr_42"
}

A line-based diff reports several changes. Semantically, there are none.

JSON objects are unordered collections of name and value pairs. That means a structural comparison should match object members by name, then compare their values, rather than treating their serialized position as meaningful. RFC 8259 distinguishes this from arrays, which are ordered sequences.

This is why formatting and key sorting are not enough for API reviews. They can make a document easier to read, but they cannot tell you whether the underlying structure changed.

Array order is data

Arrays need a different rule.

{
  "steps": ["validate", "charge", "notify"]
}
{
  "steps": ["charge", "validate", "notify"]
}

Those arrays contain the same strings, but they do not mean the same thing if order controls execution.

A structural JSON diff should compare array elements by index:

/steps/0: "validate" -> "charge"
/steps/1: "charge" -> "validate"

That result is more useful than saying that two lines moved. It identifies the values and the exact positions that changed.

There is one limitation worth keeping in mind. An index-based diff cannot reliably infer whether an item was moved, deleted, or replaced when the array contains similar values. It reports what it can prove at each position. If your API has stable item IDs, a domain-specific comparison may be more useful for interpreting moves.

Types matter even when the text looks close

JSON has distinct types. A number is not a string, and null is not the same as a missing property.

{
  "retryCount": 3,
  "enabled": true,
  "region": null
}
{
  "retryCount": "3",
  "enabled": "true"
}

A text-oriented comparison can bury these changes among punctuation and whitespace. A structural comparison should report them directly:

/retryCount: number -> string
/enabled: boolean -> string
/region: removed

The first two changes may affect validation, sorting, arithmetic, and client code. The last one may change the meaning of the request entirely. A missing property often means "use a default" or "leave unchanged." A property set to null can mean "clear this value."

Do not collapse those cases into one generic "changed" result.

Read paths as locations, not labels

A good diff needs paths that are short enough to scan and exact enough to act on.

JSON Pointer is a standard syntax for locating a value inside a JSON document. Each path segment begins with /.

{
  "account": {
    "preferences": {
      "newsletter": false
    }
  }
}

The value of newsletter has this JSON Pointer:

/account/preferences/newsletter

Arrays use their index:

{
  "items": [
    { "sku": "A-12" },
    { "sku": "B-43" }
  ]
}

The second SKU is:

/items/1/sku

Keys can contain characters with special meaning in a pointer. A slash in a key becomes ~1, and a tilde becomes ~0.

{
  "a/b": {
    "m~n": true
  }
}

The path is:

/a~1b/m~0n

That escape rule comes from RFC 6901. It looks unusual at first, but it lets a diff report an unambiguous path even when object keys contain slashes.

A practical API response review

Suppose an API response changes from this:

{
  "customer": {
    "id": "cus_123",
    "creditLimit": 5000,
    "status": "active"
  },
  "invoices": [
    { "id": "inv_1", "total": 1200 }
  ]
}

to this:

{
  "customer": {
    "id": "cus_123",
    "creditLimit": "5000",
    "status": "active",
    "riskLevel": "review"
  },
  "invoices": [
    { "id": "inv_1", "total": 1200 },
    { "id": "inv_2", "total": 300 }
  ]
}

The useful review output is not a long block of changed lines. It is this:

/customer/creditLimit: number -> string
/customer/riskLevel: added, value "review"
/invoices/1: added object

That gives a developer three concrete questions:

  1. Should creditLimit still support numeric operations?
  2. Does the client need to handle riskLevel?
  3. Is the new invoice expected, or did pagination or filtering change?

A structural diff does not answer those questions. It makes sure they do not get lost.

Avoid misleading comparisons

Before comparing JSON, validate both inputs as strict JSON.

Duplicate object names are a problem because parsers may handle them differently. Large integer literals can also lose precision in JavaScript before a comparison begins. A tool should stop and explain those cases instead of silently producing a report that looks trustworthy.

Keep the comparison scope clear too:

  • A JSON diff is not schema validation.
  • A JSON diff is not an API compatibility guarantee.
  • A JSON Pointer report is not a JSON Patch document.
  • Equal JSON structures do not prove that business rules are still correct.

Use a structural diff to find changes. Use tests, schemas, and the real integration to decide whether those changes are acceptable.

Compare structure without uploading the documents

I built the ToolExo JSON Diff Checker for this workflow. It parses both JSON documents locally, compares object members by key and arrays by index, and reports added, removed, changed, and type-changed values with JSON Pointer paths.

Whitespace and object member order do not create differences. Array order, null, missing properties, value changes, and type changes remain visible.

That is the level of comparison most API reviews need. Less noise, clearer paths, and fewer cases where a meaningful change disappears inside a formatting diff.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.