JSON Best Practices

Any valid JSON parses. Whether it stays maintainable for the next five years comes down to a handful of conventions about naming, types and structure. They cost nothing to adopt at the start and get expensive to retrofit once other people’s code depends on your format. These are the ones that consistently pay off.

Pick one key naming style and never deviate

{ "firstName": "Ada", "createdAt": "2026-07-22T09:30:00Z" }   // camelCase
{ "first_name": "Ada", "created_at": "2026-07-22T09:30:00Z" } // snake_case

Both are fine. Mixing them is not.

camelCase dominates JavaScript-centric APIs, while snake_case is common in the Python and Ruby ecosystems, and Stripe and GitHub both use it. Whichever you pick, apply it to every key, write it down, and enforce it in review. Avoid keys with spaces or punctuation, since they force bracket access in most languages, and never encode data in a key name ("user_1234": {…}). Make it a value instead.

Dates are ISO 8601 strings, in UTC

"createdAt": "2026-07-22T09:30:00Z"   ✓
"createdAt": 1784107800               △ meaning unclear: seconds? ms?
"createdAt": "07/22/26"               ✕ ambiguous

JSON has no date type, so the job is to pick the least ambiguous representation available. ISO 8601 with an explicit UTC Z sorts lexicographically, parses in every language, and can be read by a human staring at a log file at 3 a.m.

Money and precision-sensitive numbers

JSON numbers are typically parsed as floating point, and floats cannot represent a value like 0.1 exactly. For money, use integer minor units ("amountCents": 1999) or a string ("amount": "19.99"), which is the convention payment APIs settled on. The same applies to 64-bit IDs: JavaScript loses precision above 2^53, which is why Twitter’s API famously shipped id_str alongside id.

Decide what null means, then hold the line

A field that is null and a field that is absent carry different signals: “this has no value” versus “this wasn’t provided.” Pick a policy and apply it everywhere. A common one for PATCH requests treats null as “clear this field” and absence as “leave it alone.” Steer clear of other sentinel values too. Empty strings standing in for null, or -1 meaning “unknown,” always leak into display code eventually.

Keep structure shallow and lists uniform

Every nesting level is a hoop consumers have to jump through, and two or three levels covers most data honestly. Prefer arrays of objects with identical shapes, because uniform records are what CSV converters, table renderers and type generators like the JSON to TypeScript tool expect. When items in one array have wildly different keys, that is usually two lists wearing one name.

Format for the reader; sort for the diff

JSON that humans maintain belongs in version control formatted, with 2-space indentation as the ecosystem default. Sorting keys alphabetically, which the JSON Sorter does recursively, gives files a canonical form so diffs show real changes rather than reordering noise. Machine-to-machine payloads go the other way and ship minified, since whitespace there is pure transfer cost.

Security and robustness basics

Always parse with a real parser, never eval(), and treat inbound JSON as untrusted input: enforce size limits, validate the structure (JSON Schema formalizes this), and don’t assume fields exist just because they usually do. Duplicate keys are technically tolerated by parsers, where the last one wins, but they are always a bug in whatever produced the document. The JSON Validator catches outright syntax problems, and consistent formatting makes the logical ones visible. For the syntax rules themselves, see How to Fix Common JSON Errors.

Sources and further reading