2026-08-26 · 8 min read
- json
- api
- schema
JSON has a small type system: object, array, string, number, boolean, and null. That simplicity makes APIs portable, but it also leaves important decisions to each contract. Is an account ID a number or a string? Is an absent field different from null? Can a decimal arrive in scientific notation?
Stable answers matter more than the choice itself. A producer that changes types between requests forces every consumer to add coercion, and coercion can turn a visible contract failure into silent data corruption.
Choose types from domain behavior
Use numbers for values that clients calculate and strings for identifiers that clients compare but never add. Strings preserve leading zeros and integers beyond JavaScript's safe range. Decimal money needs an explicit representation because binary floating-point cannot exactly represent every base-10 value.
- Represent opaque IDs as strings, even when a database currently uses integers.
- Define units beside numeric quantities instead of relying on field-name folklore.
- Use booleans rather than "true", "false", 0, or 1 when the wire contract allows it.
- Document money as decimal strings, minor-unit integers, or another deliberate model.
Define missing, null, and empty separately
An absent field can mean not requested, unknown, or unchanged in a patch. Null can mean explicitly empty. An empty string or array is a present value. Treating all four states as equivalent causes accidental data erasure and confusing partial updates.
Write these semantics into the schema and test them at update boundaries. PATCH endpoints especially need a clear rule for omission versus explicit clearing.
Trace type drift through the stack
When production types change, follow the value from storage to the wire. Database drivers, object mappers, form libraries, CSV readers, and JSON serializers can each introduce a conversion.
- Format a sanitized payload so quotes and nulls are visible.
- Diff known-good and failing fixtures after normalizing volatile fields.
- Compare the payload with OpenAPI, JSON Schema, or generated client types.
- Inspect migrations and serializer settings at the first changed boundary.
Build contract tests that catch shape changes
Keep representative response fixtures and validate them in CI. Include maximum IDs, zero, negative values where valid, nulls, omitted fields, decimals, and mixed-version clients. A happy-path example alone rarely exercises the type decisions most likely to drift.
Use Flashman's JSON formatter to inspect wire data, the diff tool to compare fixtures, the case converter for naming changes, and the units converter to verify scale changes. All processing remains in the browser, but production payloads should still be sanitized before sharing.