Why Valid JSON Breaks: Commas, Numbers, and Escapes
Why Valid JSON Breaks: Commas, Numbers, and Escapes
JSON is stricter than it looks. What a parser error position really tells you, why long IDs change value, and what a formatter can never catch.
Original workflow visual
Why Valid JSON Breaks: Commas, Numbers, and Escapes
Understand
Review before moving forward
Check
Review before moving forward
Apply
Review before moving forward
Four things that are legal in JavaScript are illegal in JSON: a trailing comma after the last element, single-quoted strings, unquoted object keys, and comments. NaN and Infinity are not valid numbers either, and neither is a leading plus or a leading zero. Most hand-edited JSON fails on exactly one of these. If a config file was written by a person rather than a program, check the last element of every array and object first — the trailing comma is by far the most common single cause.
A parser reports the offset where it gave up, which is usually just after the real mistake rather than on it. An unterminated string is reported at the end of the file, because the parser kept consuming characters looking for a closing quote. An unexpected token at line 40 often means a comma is missing at line 39. Format the document first: once the structure is indented, the eye finds the unbalanced brace far faster than by counting characters.
JSON does not define a numeric precision, so nearly every parser reads numbers into an IEEE-754 double. That holds integers exactly up to 2^53, about 9.007 quadrillion. A 19-digit Twitter-style ID or a Snowflake ID exceeds that, so it is silently rounded and the last digits change. The payload stays valid and the value is now wrong. Any identifier that will not fit in 53 bits should be transported as a string, and the same applies to money if you are storing minor units in a large integer.
The specification does not say what happens when an object contains the same key twice. Most parsers keep the last occurrence, some keep the first, and a few raise an error. This matters when payloads are assembled by string concatenation or merged from templates, because the duplicate is invisible in the rendered output. Key order has the same ambiguity: objects are unordered by definition, so sorting keys is useful for comparing two payloads but must never be relied on for meaning.
Inside a string, a backslash, a double quote, and control characters below U+0020 must be escaped. The \uXXXX form encodes one UTF-16 code unit, which means characters outside the Basic Multilingual Plane — emoji, rarer CJK characters — need a surrogate pair of two escapes. A lone surrogate is technically malformed and will produce a replacement character somewhere downstream. If Chinese or Japanese text arrives as question marks or boxes, the encoding broke before JSON was involved; the payload is a symptom, not the cause.
Formatting is for humans reading a payload; minifying is for bytes on the wire. The saving from minifying is smaller than it looks once transport compression is on, because gzip and brotli handle repeated whitespace efficiently. Minify at the point of sending, keep the formatted version in source control, and never store a minified payload as the file a person is expected to edit.
A formatter answers one question: is this well-formed JSON. It cannot tell you whether a required field is missing, whether a string should have been a number, or whether an empty array means "none" or "not loaded". Those are schema questions. A payload that formats perfectly can still be rejected by the API you are sending it to, so treat a successful format as the beginning of debugging rather than the end.
Common Questions
The grammar has no production for it. JavaScript tolerates it, JSON does not, and it is the single most common cause of a hand-edited file failing to parse.
It was parsed into a double. Integers above 2^53 cannot be represented exactly, so the low digits are rounded. Send identifiers of that size as strings.
Not in standard JSON. Some tools accept JSON5 or JSONC, but anything sent over an API should assume comments are a parse error. A dedicated "_comment" key is the usual workaround.
Only if you are not already using transport compression. Gzip removes most of the cost of pretty-printing, so readability is usually the better trade.