JSON

JSON in Practice: Strict Syntax, Sloppy Reality, and the Errors In Between

· 3 min read

JSON won the data-format wars by being small: the entire grammar fits on a business card (json.org famously prints it on one page). Objects, arrays, strings, numbers, true, false, null — that is the whole language. Yet 'invalid JSON' remains one of the most common errors in day-to-day development, precisely because the grammar is stricter than the JavaScript it grew out of and stricter than what humans naturally type.

The gap between what looks like JSON and what parses as JSON is where the bugs live. This guide maps that gap.

What the spec actually says

RFC 8259 (and the identical ECMA-404) defines JSON tightly. Strings use double quotes only, with backslash escapes for quotes, backslashes, and control characters. Object keys are strings — always quoted. Numbers are decimal: no leading zeros (01), no bare dot (.5), no hex, no NaN or Infinity. The three literals are lowercase: true, false, null. Whitespace between tokens is free; everything else is not.

Two liberalizations are worth knowing. Since RFC 7159 (2014), any JSON value is a valid top-level document — "hello", 42, and null are complete JSON texts, not just objects and arrays. And the spec deliberately leaves duplicate keys 'undefined behavior': {"a":1,"a":2} parses everywhere but means different things to different parsers (JavaScript keeps the last value). Treat duplicates as data corruption even though no error is raised.

Where invalid JSON comes from

Almost nobody hand-types broken brackets. Invalid JSON is manufactured by predictable processes:

  • Pasting code: JavaScript object literals (unquoted keys, single quotes, trailing commas, comments) and Python reprs ({'key': True, 'x': None}) are not JSON. Serialize properly: JSON.stringify, json.dumps.
  • Word processors: smart quotes (“ ”) look identical to straight quotes at reading distance and fail instantly at parse time.
  • Truncation: loggers cap line length (often 8–16 KB) and cut payloads mid-string — 'Unexpected end of JSON input' is usually this.
  • Concatenation: two objects back to back ({}{}) come from log streams; that is NDJSON, a different format needing per-line parsing.
  • String-building: constructing JSON with + and template strings instead of a serializer, which breaks the first time a value contains a quote.

Numbers: the silent precision trap

JSON's grammar puts no limit on number size, but JavaScript parses every number into an IEEE-754 double with 53 bits of integer precision. Any integer above 9,007,199,254,740,991 (2^53 − 1) is silently rounded: a 64-bit database ID like 9223372036854775807 becomes …5808. No error, no warning — just corrupted identifiers that mostly work until two different IDs round to the same value.

The ecosystem's answer is consistent: serialize 64-bit IDs as strings ("id":"9223372036854775807"), as the Twitter API famously started doing (id_str) after this exact bug bit thousands of clients. If you see large IDs ending in suspicious zeros after a round-trip, this is why. The same double representation explains 0.1 + 0.2 style artifacts in decimal data — money belongs in strings or integer cents, not JSON floats.

Reading parser errors like a native

Modern engines give position information — 'Unexpected token } at position 47' or line/column — but the reported position is where the parser noticed, which for some mistakes is one token after the cause. A missing comma between properties is reported at the next key's opening quote. The habit that pays: look at the reported position, then scan backwards one token.

The greatest hits translate directly: Unexpected token ' means single quotes; Unexpected token N/u/I means NaN, undefined, or Infinity leaked from JavaScript; Unexpected end of input means truncation; Unexpected token < means an API returned an HTML error page instead of JSON — check the HTTP status, not the JSON. Bad control character means a raw newline inside a string that should be \n.

Working habits that eliminate the whole class

Never build JSON by string concatenation — serializers exist in every language and handle escaping, encoding, and nesting correctly. Validate at system boundaries, not deep inside business logic, so errors point at the real source. For configs edited by hand, format after every edit; a formatter doubles as a validator and catches the missing comma while you still remember where you typed. And when data must be inspected, use tools that parse with the same strict grammar your runtime uses — what passes there will pass in production.

← All guides