Detecting Breaking Changes in a JSON API Response

By · Published · Updated

For a JSON API response, a breaking change is one that makes existing consumer code return a wrong result, throw, or misbehave, without the consumer changing anything. Some changes that look alarming don't qualify. Some small ones do.

Changes that break consumers

Changes that usually don't break anything

Triage: additive, subtractive, or type-changing

Putting a change in one of three buckets tells you how fast to look at it, before you know whether anything downstream will break:

Semver won't catch response-shape drift

Semantic versioning describes a package's or contract's declared interface. A major version bump says "we changed something on purpose and we're calling it breaking." It says nothing about whether the implementation still matches that interface on every request.

Advertisement

Response drift is nearly always an accident from the producer's side. A refactor changes what a database query returns. A conditional path stops setting a field for some combination of inputs. A third-party service the API proxies starts returning a new value. None of these feel like a decision to make a breaking change, so none of them trigger a version bump. Nobody thought they were touching the contract. Semver covers intentional, documented changes and has no way to catch accidental ones. That's why drift has to be found by diffing real responses, not assumed safe because the version number stayed put.

Two cases the rubric doesn't sort cleanly

A field going from optional to always present looks harmless under the additive/subtractive split, since nothing that used to be there is missing. It can still break a test fixture or mock that left the field out on purpose to exercise the "missing" path. Production code is fine. The test suite breaks.

The other is null versus an absent key. {"note": null} and {} (no note key) mean different things in JSON, but several languages' JSON libraries turn both into the same empty value by default. So a producer that switches from always sending null to sometimes leaving the key out can look identical from inside consumer code, until something checks 'note' in response instead of checking the value.

One-off diffs vs. drift over time

These are separate jobs, and this site has a tool for each. The JSON Diff Viewer is for when you already have both sides: a response from before a deploy and one from after, or two environments you're trying to reconcile. It keeps nothing between visits. You paste two payloads and get a diff.

Live Types is for when you don't yet know something changed. You save a snapshot of a response's inferred shape while it's known to be good. Later, when you paste a fresh sample from the same endpoint, it diffs the new shape against the saved one and lists the fields that were added, removed, or changed type.

This is how the sometimes-absent field from earlier shows up. Pasting several fresh samples over time reveals a field that's only missing some of the time. A single before/after diff can't, because it only sees one sample on each side.

What this doesn't automate

Both tools run when you paste something. Neither polls a live endpoint in the background. If you want drift checked on every deploy or on a schedule, you need a CI step that diffs against an exported snapshot, which is separate work from either tool here. The browser tools stay snapshot-and-compare because there's no server behind them to poll anything, and nothing you paste leaves your machine.

Advertisement

Try the JSON Diff Viewer →

Related tools & guides

Part of JSON Tools: Format, Validate, Diff, Convert, and Generate Types