Stage 3. JSON in API
In API design, JSON is a public contract, not just a transport format. When this contract is unstable, the impact is broad: mobile releases break against backend changes, partner integrations fail after "small" updates, and analytics receives inconsistent data. A production API must treat JSON as a managed interface.
In plain terms, JSON is a text format built from key: value pairs, objects {}, and arrays []. Its main practical benefit is interoperability: the same payload is readable and parseable across different languages and platforms. For beginners, two syntax rules are critical: JSON keys must be strings in double quotes, and commas separate elements but never appear after the final item. Many early integration issues come from violating these basics.
A core discipline is separating internal models from external DTOs. Internal structures should evolve as needed for domain and storage changes. External contract shape should evolve carefully and predictably. Returning internal entities directly removes that control and makes refactoring risky.
Another key discipline is separating input-processing stages. First, parse and validate JSON structure. Next, validate business constraints. Then execute domain logic. If these layers are mixed, clients cannot understand why a request failed: syntax issue, type mismatch, or domain-rule violation.
{
"email": "[email protected]",
"age": 27
}
It is useful to distinguish “valid JSON” from “valid business data.” For example, {\"age\": \"twenty\"} can be syntactically valid JSON text and still be invalid for your API if age must be numeric. Keeping this distinction explicit makes error handling much clearer for clients.
Error format consistency is as important as success payload consistency. Stable error schemas allow frontend and mobile clients to implement reusable handling paths instead of endpoint-specific hacks.
{
"error": "validation_failed",
"fields": {
"email": "must be valid"
},
"traceId": "d1f1c9a2"
}
Date/time fields are a frequent hidden failure source. If one endpoint returns UTC timestamps, another returns local time without timezone, and a third uses custom text, reporting and filtering logic diverges across systems. These bugs are costly because they surface as business inconsistencies, not obvious crashes.
Contract evolution needs clear rules. A safe strategy is to add new fields as optional, avoid deleting required fields without a migration window, and avoid silent type changes. For critical APIs, versioning policy should be explicit so server and clients can evolve in sync.
Readable payload design also matters. If a field can only be interpreted by reading backend code, contract quality is low. Clear naming and stable typing are usually better than compact but ambiguous schemas.
Another practical contract rule: avoid one “universal schema” for all operations. The same entity usually needs different representations for list view, detail view, create request, and update request. Defining operation-specific schemas keeps API evolution safer and prevents accidental coupling between unrelated endpoints.
Testing should cover more than happy paths. Valuable coverage includes invalid type handling, missing required fields, unknown fields, and response-shape regression checks. Even lightweight snapshots on high-traffic endpoints can prevent accidental breaking changes during internal refactors.
Practical scenario
A team adds createdAt as local time without timezone. UI looks correct in one region, but cross-region reports drift. Investigation shows that downstream services interpret time differently. The failure is not in business code; it is in an under-specified JSON contract. A single explicit time-format standard would have prevented the incident.