JSON Formatting Best Practices for Developers
Strong opinions on JSON formatting, indentation, key naming, and the conventions that separate readable APIs from painful ones.
By Syed Husnain Haider Bukhari · · Updated
JSON is the lingua franca of modern APIs. It's simple enough that a developer can read it without documentation and structured enough that machines can parse it reliably. But "simple" doesn't mean "foolproof." The same conceptual data can be represented in JSON dozens of different ways, and the wrong choices make APIs awkward to consume, error-prone to debug, and harder to evolve.
This post collects the formatting and design conventions that consistently produce JSON people enjoy working with. Most are opinionated — there isn't a single objective "correct" answer for many of these — but the recommendations here align with what mature APIs from Stripe, GitHub, Twilio, and similar shops do in practice.
Indentation: two spaces for humans, none for the wire
JSON has no whitespace requirements — `{"a":1,"b":2}` and a pretty-printed version with newlines and indentation are semantically identical. Use minified JSON over the network: it's smaller, faster to gzip, and parsers don't care. Use pretty-printed JSON in logs, examples, documentation, and anywhere a human will read it.
For pretty-printing, two spaces of indentation is the dominant convention. Tabs work but cause display issues across editors; four-space indentation eats horizontal space for deeply nested structures. Most JSON formatters default to two spaces for good reason.
Key naming: pick one convention and stick to it
JSON itself imposes no rules on key naming. In practice three conventions dominate: snake_case (`user_id`), camelCase (`userId`), and kebab-case (`user-id`). JavaScript ecosystems lean camelCase; Ruby and Python ecosystems lean snake_case; HTTP headers and URLs use kebab-case.
The single most important rule is consistency. Mixing camelCase and snake_case in the same response is the most common avoidable mistake in API design. It forces every consumer to write defensive code that handles both. Pick one and enforce it with a linter on every endpoint.
If you're not sure which to pick: camelCase if your primary consumers are JavaScript clients, snake_case if Python or Ruby, kebab-case if you're serving config files meant to be edited by hand.
Null vs missing vs empty
JSON has three ways to represent "no value": the key is absent, the key is present with value `null`, and the key is present with an empty string or zero. These should mean different things, and consumers will assume they do.
A reasonable convention: omit the key entirely when the field is genuinely not applicable; use `null` to indicate "applicable but unknown"; use an empty string only when an empty string is a valid value distinct from null. For arrays, prefer `[]` over `null` for "no items" — it spares consumers a null check before every iteration.
Dates: use ISO 8601, always with timezone
Date formatting is one of the most consistent sources of bugs in JSON APIs. The right answer is ISO 8601 with explicit timezone: `"2026-05-21T14:32:00Z"` for UTC, or `"2026-05-21T14:32:00+02:00"` for local time. Avoid Unix timestamps as numbers — they're terse but force every consumer to remember the unit (seconds? milliseconds?) and can't represent dates before 1970 or after 2038 (in 32-bit form).
Avoid locale-dependent date strings like `"5/21/2026"`. Avoid date-only fields that include a time component (`"2026-05-21T00:00:00Z"`) — use `"2026-05-21"` instead. The fewer ambiguities your dates carry, the fewer bugs your consumers will ship.
Numbers: don't trust JSON for big integers
JavaScript numbers are 64-bit floats, which means JavaScript can only safely represent integers up to 2^53 (about 9 quadrillion). Many backends use 64-bit integers that exceed this. If your IDs are 64-bit integers (Twitter snowflake IDs, for example), serialize them as strings: `"id": "1493723847234923847"`. Otherwise JavaScript clients silently truncate the value during parsing.
Money is another classic trap. Represent currency as integer cents (`{"amount": 1234, "currency": "USD"}` for $12.34) or as a precise string (`"amount": "12.34"`), never as a float. Float arithmetic on monetary values produces rounding bugs that get expensive.
Errors: structured, not just strings
An error response should be a JSON object with a stable shape, not a plain string. A good error format includes an error code (machine-readable), a human-readable message, and ideally a documentation URL. Stripe's error format is widely copied for good reason:
`{"error": {"code": "insufficient_funds", "message": "Card declined.", "doc_url": "https://stripe.com/docs/error-codes/insufficient-funds", "request_id": "req_abc123"}}`. Codes let consumers branch on specific errors. Messages let users see what happened. Request IDs let support find the failing request in your logs.
Pagination: cursors over offsets at scale
Pagination via `?page=2&per_page=20` is fine for small data sets but breaks down when data changes between page loads (new records shift the offset, causing duplicates or skips). Cursor pagination — passing an opaque token that refers to a specific record — is more reliable.
A cursor-paginated response looks like `{"data": [...], "next_cursor": "eyJpZCI6MTIzNH0", "has_more": true}`. Consumers don't decode the cursor; they just pass it back in the next request. This pattern survives concurrent writes gracefully.
Validating and debugging JSON quickly
When JSON looks broken, the issue is usually one of four things: a trailing comma (JSON doesn't allow them), single quotes instead of double quotes, unescaped newlines inside a string, or a stray character outside the root object (often a BOM or whitespace).
A good JSON formatter tells you exactly which line and column the parse failed at, which usually pinpoints the issue in seconds. Browser DevTools includes a JSON viewer that highlights errors. Our free JSON formatter does the same and runs entirely in-browser, so sensitive payloads never leave your machine.
Putting it all together
JSON is forgiving, but APIs that follow consistent conventions are dramatically easier to consume and evolve. Pick a key style and enforce it. Always use ISO 8601 with timezone for dates. Stringify large integers and money. Structure your errors. Use cursor pagination when data is volatile. Pretty-print in docs, minify on the wire.
Once these conventions are baked into your generators and validators, you stop thinking about formatting and start spending time on the things that actually matter — the data, the schema, and the API surface itself.