API Contract Breaking Changes: Checking Field Changes
A changed JSON example is not automatically a broken API contract. Compatibility depends on direction, whether the data is a request or response, and what clients accept. This article is limited to server response objects and existing clients; it does not label every runtime payload difference as breaking.
1. Scope: compare a response contract first
Removing a field a client uses, changing its type, or making a previously guaranteed field absent are response compatibility risks. Adding an optional field is often safe, but adding a value the server may return to an existing enum can break a client that handles only the old set. Request parameters have the opposite direction in several cases.
2. Minimal reproduction: value change versus contract change
Changing status from active to paused is not merely a value diff when a client accepts only active or disabled. A changed order amount within the same number contract is ordinary data.
const before = { id: "string", status: ["active", "disabled"] };
const after = { id: "string", status: ["active", "disabled", "paused"] };
// Compare declared field constraints, not two live orders with different amounts.3. Root cause: JSON Diff has no consumer direction
A structural diff can report additions, removals, and replacements, but cannot know request versus response direction or whether clients exhaustively handle an enum. Contract checking adds policy. Here, required means the server guarantees a field and enum means the strings it may return; another protocol may use different rules but must document them.
4. Recommended policy: explicit, reviewable response snapshots
Describe each response field with type, required, and optional enum, keep a baseline under version control, and compare it with a candidate. Do not present an undefined compareJsonSchemas placeholder as implementation. Use a selected schema tool for OpenAPI, oneOf, ranges, and nested structures, while keeping service-specific breaking rules explicit.
5. Complete code: flat response-shape checks
This browser- and Node-compatible TypeScript deliberately supports flat response fields only. It reports removals, type changes, weaker required guarantees, and new possible enum outputs; it is not a full OpenAPI validator.
type ScalarType = "string" | "number" | "boolean" | "null";
type ResponseField = { type: ScalarType; required: boolean; enum?: readonly string[] };
type ResponseShape = Record<string, ResponseField>;
type Finding = { field: string; kind: "removed" | "type" | "required" | "enum"; message: string };
function findResponseBreakingChanges(before: ResponseShape, after: ResponseShape): Finding[] {
const findings: Finding[] = [];
for (const [name, oldField] of Object.entries(before)) {
const newField = after[name];
if (!newField) { findings.push({ field: name, kind: "removed", message: "field is no longer returned" }); continue; }
if (oldField.type !== newField.type) findings.push({ field: name, kind: "type", message: oldField.type + " changed to " + newField.type });
if (oldField.required && !newField.required) findings.push({ field: name, kind: "required", message: "field is no longer guaranteed" });
if (oldField.enum && newField.enum) for (const value of newField.enum) if (!oldField.enum.includes(value)) findings.push({ field: name, kind: "enum", message: "new response enum value: " + value });
}
return findings;
}
console.log(findResponseBreakingChanges(
{ id: { type: "string", required: true }, status: { type: "string", required: true, enum: ["active", "disabled"] } },
{ id: { type: "string", required: true }, status: { type: "string", required: true, enum: ["active", "disabled", "paused"] } },
));6. Incorrect approaches
Blocking every added field prevents compatible extension. Comparing live example values reports normal data changes, while checking only field presence misses type, required, and enum risks. Applying response rules directly to request fields is also unsafe because direction changes the compatibility analysis.
7. Boundaries: nesting, nullable values, and version policy
Nested objects need paths or recursion, and arrays need an element contract. Nullable, missing, and empty string are distinct states. An added enum value can be a warning instead of a blocker when clients have a default branch. Versioning, deprecation, and migration windows are release policy, not facts inferred by a JSON diff.
8. Verification
Write snapshots for removal, string-to-number, required-to-optional, optional addition, a new response enum value, and ordinary value changes. Assert the intended findings and ensure compatible additions are not falsely blocked. Review the baseline and candidate with the API owner.
9. FAQ
Is every added response field safe? Usually for tolerant clients, but clients that reject unknown fields need coordination.
Why can a new enum value break clients? An exhaustive switch can treat it as an error.
Does this replace OpenAPI tooling? No; it demonstrates reviewable rules for a deliberately small schema scope.
10. Summary
Breaking-change checks are about consumer direction, not coloring every JSON difference red. For responses, examine removal, type changes, weaker guarantees, and new enum outputs. Use reviewable snapshots and focused tests, then rely on suitable tooling for complex schemas.
Sources and further reading
Specifications and references for checking the article’s technical details.
Related articles
Why JSON Key Order Changes a Diff: Structural Comparison
Learn the difference between JSON text and structural comparison, with a recursive object-key canonicalizer that deliberately preserves array order and documents key boundaries.
How it worksMyers Diff for JSON Arrays: Limits and Implementation
Use Myers shortest edit scripts for ordered JSON arrays, with insert/delete/equal operations, path patches, and clear limits around object identity and duplicate values.
TroubleshootingJSON to SQL Types: MySQL and PostgreSQL
Generate conservative MySQL and PostgreSQL candidates for JSON, numbers, strings, arrays, objects, and NULL while explaining why samples cannot replace an explicit schema.
Keep exploring
Use the related browser tool to test the workflow with your own sample data.
Open related tool