Reading and Validating JSON Without Breaking It
JSON (JavaScript Object Notation) is a text format for structured data. Its entire grammar is small enough to hold in your head, and nearly every "invalid JSON" error comes from a short list of habits carried over from JavaScript or Python. This guide covers the whole grammar and those failure modes.
The complete grammar
A JSON document is exactly one value. A value is one of:
- object —
{ }containing zero or more"key": valuepairs separated by commas. Keys must be double-quoted strings. - array —
[ ]containing zero or more values separated by commas. - string — double quotes only. Certain characters must be escaped with a backslash:
\",\\,\/,\b,\f,\n,\r,\t, and\ufollowed by four hex digits. - number — decimal, optional minus, optional fraction, optional
e/Eexponent. No leading+, no leading zeros, no hex, noNaN, noInfinity. - boolean —
trueorfalse, lowercase. - null — lowercase.
Whitespace between tokens is ignored. There are no comments. That is the whole specification.
The mistakes that cause almost every parse error
| Invalid | Why | Fix |
|---|---|---|
{ "a": 1, } | Trailing comma after the last pair | Remove it |
{ 'a': 1 } | Single-quoted key or string | Use double quotes |
{ a: 1 } | Unquoted key | Quote it: "a" |
// note or /* */ | Comments are not allowed | Delete, or use JSON5/JSONC deliberately |
{ "a": .5 } | Number with no leading digit | 0.5 |
{ "a": 1. } | Trailing decimal point | 1.0 or 1 |
{ "a": undefined } | undefined is not a JSON value | null, or omit the key |
{ "a": NaN } | NaN/Infinity are not valid | Use null or a string sentinel |
Smart quotes “ ” | Pasted from a word processor | Replace with straight " |
| A leading U+FEFF byte-order mark | Some editors add it to UTF-8 files | Save without BOM |
A formatter that reports the line and column of the first error saves a lot of time here — paste the document into the JSON formatter and it will pretty-print valid input or point at the exact character that broke.
Numbers deserve special care
JSON numbers are arbitrary-precision in the spec, but most parsers load them into a 64-bit float. That means integers beyond 253 lose precision silently: an ID like 9007199254740993 may read back as ...992. If a value is an identifier rather than a quantity, transmit it as a string. The same applies to money — represent it in minor units as an integer, or as a string, not as 19.99 which is not exactly representable in binary floating point.
Duplicate keys
The spec does not forbid { "a": 1, "a": 2 } but it does not define what it means either. In practice most parsers keep the last value. Do not rely on this — deduplicate before serializing.
Validating structure, not just syntax
Syntactic validity ("is this parseable JSON") is different from structural validity ("does it have the fields my program expects, with the right types"). For the second, use JSON Schema — a JSON document that describes the shape another document must match, with keywords like type, required, properties, enum, and minimum. It is the standard way to reject bad data at an API boundary before it reaches your logic.
Related formats
- JWT — three base64url-encoded parts, the first two of which are JSON. The JWT decoder unpacks the header and payload so you can read the claims.
- YAML — a superset of JSON in practice, with indentation instead of braces and support for comments. The YAML ↔ JSON converter moves between the two.
- NDJSON / JSON Lines — one JSON value per line, no enclosing array. Good for streaming and logs; not itself a single valid JSON document.