JSON Best Practices: Formatting, Validating, and Shipping JSON Without Self-Inflicted Wounds
JSON is simple enough that people stop thinking about it — and that's exactly where the data corruption happens. These are the practices that keep API payloads boring, in the good sense.
Paste first, commit second
The cheapest JSON bug filter is visual. Before committing a config file, API fixture, or README sample, run it through the validator — it locates syntax errors by line and column and shows you where a human eye will give up. The repair mode can even fix trailing commas and unquoted keys, but treat repair as a diagnostic, not a workflow: if you need repair, you need a formatter first.
Numbers are not numbers
JSON numbers parse to IEEE-754 doubles, which hold integers exactly only up to 2^53. Snowflake IDs, Twitter IDs, and nanosecond timestamps silently lose precision in JSON.parse:
{ "id": 12345678901234567890 } // parsed: 12345678901234568000
If an ID exceeds 15–16 digits, it must be a string on the wire. This one line has prevented more incident tickets than any style guide.
Dates: one format, spelled out
"2026-10-01T15:30:00Z" (RFC 3339 / ISO 8601, UTC, Z) or nothing. Unix seconds vs milliseconds vs epoch-2010 integers is a three-way compatibility bug waiting for the first client written against the wrong convention.
Null and missing are different answers
{"moderator": null} means "answered: there is none." {} means "not asked." APIs that collapse the two force clients to guess intent. Pick a convention, document it, and keep it — this is the JSON equivalent of three-valued logic, and every ORM eventually makes you face it.
UTF-8, always, and don't double-encode
JSON is Unicode; files are UTF-8; Content-Type: application/json implies it. The classic corruption is a proxy that escapes to \uXXXX then another layer escapes the backslashes — you get "\\u00e9" where a name should be. One pass of the beautifier reveals double-escaping instantly: if you see literal \u sequences where accented characters belong, count your backslashes.
Pretty for humans, minified for the wire
Pretty-printed JSON is 30–60% larger than minified (indentation and newlines are pure overhead on the wire). Rule of thumb:
- On the wire: minified, plus gzip — JSON compresses ~85%.
- In git: pretty, 2-space indent, keys in stable order. Diff noise from reordered keys is a review tax everyone pays forever.
- In logs: minified and truncated — full pretty-printed blobs in logs are how log budgets die.
Trailing commas: invalid, and that's the tradeoff
JSON deliberately dropped trailing commas that JavaScript allowed, so {a: 1,} fails parse in every non-JS consumer — and the validator will show you the exact column. JSON5/JSONC (comments, trailing commas) are fine for editor configs your toolchain parses deliberately; they are not fine for an API contract.
FAQ
Should API responses wrap payloads in a data key? When you might add siblings (meta, errors, links), yes — a top-level array can't evolve without breaking clients. When the response is the collection and versioning handles evolution, no, don't ceremonial-wrap it.
Is key order significant? Per spec, no — objects are unordered maps. For git-diffable files, stable sorted keys beat "insertion order" every time. The beautifier can sort keys for you.
Why does JSON.parse succeed on my file that jq rejects? Browsers and Node implement a superset tolerance for a few Unicode edge cases; strict parsers like jq follow RFC 8259 byte-precisely. Trust the stricter one for portability.