JSON: The Spec Is Small, the Edge Cases Are Not
Numbers that lose precision, duplicate keys, and why you cannot store a date.
JSON has six types: object, array, string, number, boolean, null. There is no date, no integer, no comment and no trailing comma. Most of the difficulty people have with it comes from those absences.
Numbers are the sharp edge
JSON does not distinguish integers from floats — there is one number type, and most parsers map it to a 64-bit float. That gives exact integers only up to 253−1, which is 9,007,199,254,740,991.
Anything larger loses precision silently. A 64-bit database ID, a Twitter-style snowflake ID, or a value in satoshis will parse without error and come back changed:
{"id": 9007199254740993} → parsed as 9007199254740992
No exception, no warning, a different record. The fix is to transport large integers as strings. Every large API that learned this the hard way now does so.
Floating point brings the usual companion: 0.1 + 0.2 is not 0.3, so monetary amounts belong in integer minor units or in strings, never as JSON numbers.
There is no date type
Dates travel as strings, which means the format is a convention you have to agree on. ISO 8601 with an explicit offset — 2026-03-14T09:30:00Z — is the one to use. A bare 2026-03-14 is ambiguous about zone, and a Unix timestamp is fine but must be labelled with its unit.
Duplicate keys are undefined
{"a": 1, "a": 2} is valid JSON and the spec does not say which wins. Most parsers take the last, some take the first, some error. This has been used to smuggle values past validators that read one key while the consumer read another, so a parser that rejects duplicates is worth having on untrusted input.
Key order is not guaranteed
Objects are unordered by definition. Most implementations preserve insertion order in practice, but nothing obliges them to, and canonical serialisation for signing or hashing must sort keys explicitly — otherwise the same data produces two different digests.
What JSON is not
- Not JavaScript. It is a subset.
NaN,Infinityandundefinedare all invalid JSON despite being valid JavaScript, which is whyJSON.stringifyquietly dropsundefinedproperties and turnsNaNintonull. - Not a config format, though it is used as one. No comments and no trailing commas make hand-editing unpleasant — hence JSON5, JSONC and the widespread use of YAML or TOML instead.
- Not a streaming format. A JSON document must be read to the closing brace before it is valid. For logs and large exports, newline-delimited JSON — one object per line — is what you actually want.
Parsing untrusted input
Deeply nested arrays can exhaust the stack in recursive-descent parsers, and a large document can exhaust memory before any of your validation runs. Limit body size at the edge, and prefer a parser with a configurable depth limit. Never hand untrusted JSON to an evaluator — eval on JSON-shaped text is remote code execution with extra steps.
Formatting for humans and machines
Pretty-printing with two-space indentation is for reading and for diffs. Minified is for the wire, and the difference on a large payload is substantial — though gzip narrows it considerably, so minifying a response that is already compressed buys less than people assume.
Format and validate JSON → · Related: Unix time · Back to all articles