Reading JSON errors, and the four rules people break
What JSON does and does not allow, why a trailing comma breaks a whole file, and how to find the actual problem from a parser's line number.
Last reviewed · 1,367 words
In short
- JSON has no comments, no trailing commas, no single quotes and no unquoted keys. Those four account for most parse errors.
- Every key must be a double-quoted string. Values may be string, number, object, array, true, false or null — and nothing else.
- The reported error position is where parsing failed, which is usually just after where the mistake is.
- JSON numbers cannot be NaN or Infinity, and very large integers lose precision when parsed into a JavaScript number.
- Minifying strips whitespace and typically saves 15% to 30%; formatting adds it back for reading.
JSON is deliberately minimal. It has six value types, two container types, and no features. Almost every error comes from expecting it to allow something that a programming language allows and JSON does not.
The structure
{
"name": "Priya",
"age": 30,
"active": true,
"score": null,
"tags": ["admin", "editor"],
"address": { "city": "Pune", "pin": "411001" }
}
Objects use braces and hold key-value pairs. Arrays use square brackets and hold ordered values.
Values may be a string, a number, an object, an array, true, false or null. That is the complete list.
Keys must be double-quoted strings, always.
The four rules people break
No comments. JSON has none. Not //, not /* */, not #. Douglas Crockford removed them deliberately, on the reasoning that people would use them to carry parsing directives.
The workaround where a comment is genuinely needed is a key that is ignored — "_comment": "..." — which is a convention rather than a feature. For configuration files, JSON5, JSONC or YAML support comments properly and are better suited.
No trailing commas. [1, 2, 3,] is invalid. So is a comma after the last pair in an object. Many languages permit it and JSON does not, which is why reordering lines in a config file breaks it so often.
Double quotes only. 'name' is invalid; "name" is required. Both keys and string values.
Keys must be quoted. {name: "Priya"} is valid JavaScript and invalid JSON.
Those four cover the large majority of parse failures, and all four are things the author's editor probably allows in the language they were just writing.
Reading the error message
Parsers report the position where parsing failed, which is generally after the actual mistake.
Unexpected token } at position 47
A missing comma at position 30 is not detected until the parser reaches something it cannot reconcile — frequently the closing brace. Look backwards from the reported position, not at it.
Common messages and what they usually mean:
| Message | Usual cause |
|---|---|
| Unexpected token } | Trailing comma before it, or a missing value |
| Unexpected end of input | An unclosed brace, bracket or quote |
| Unexpected token in JSON at position 0 | The response was not JSON at all |
| Expected property name or } | An unquoted key, or a trailing comma |
| Bad control character in string | A literal newline or tab inside a string |
Position 0 deserves its own note. It almost always means the server returned HTML — an error page, a login redirect, a proxy notice — and the code tried to parse it as JSON. The fix is not in the JSON; it is in whatever produced the response.
Numbers, and where they surprise
JSON numbers are decimal, optionally with a fraction and an exponent. Three things are not allowed:
NaN and Infinity are invalid. A language that produces them must serialise them as null or as strings.
Leading zeros are invalid: 007 must be 7.
Hexadecimal is invalid: 0xFF must be 255.
The subtler problem is precision. JSON itself places no limit on a number's size, but JavaScript parses every number into a 64-bit float, which represents integers exactly only up to 2⁵³ − 1 — 9,007,199,254,740,991.
A 64-bit database ID beyond that silently loses its last digits on parse. 9007199254740993 becomes 9007199254740992, and nothing reports an error.
The fix is to transmit large IDs as strings. Most APIs handling 64-bit identifiers do exactly this, and it is worth doing before it becomes a support ticket about records that cannot be found.
Strings and escaping
Certain characters must be escaped inside a string:
| Character | Escape |
|---|---|
| Double quote | \" |
| Backslash | \\ |
| Newline | \n |
| Tab | \t |
| Carriage return | \r |
A literal newline inside a string is invalid — it must be \n. This is what produces "bad control character" errors, and it happens when multi-line text is inserted into JSON without escaping.
Unicode may be written directly as UTF-8 or escaped as \u followed by four hexadecimal digits. Characters outside the Basic Multilingual Plane — most emoji, and some historic scripts — need a surrogate pair of two \u escapes.
Forward slashes may be escaped as \/ and do not need to be. Some serialisers do it as a legacy habit from embedding JSON in HTML script tags, where </script> inside a string would end the tag early.
Formatting and minifying
Formatting adds indentation and line breaks. Two spaces is the most common convention, four is also standard, and tabs are used less often.
Minifying removes all non-essential whitespace, typically saving 15% to 30% of the size. Worth doing for anything transmitted over a network and unhelpful for anything a person reads.
Since JSON has no comments, minifying loses nothing but readability — which is one of the few practical advantages of its austerity.
For very large files, streaming parsers process the document incrementally rather than loading it whole, and formatting tools that hold the entire structure in memory will struggle well before a streaming reader does.
What JSON does not have
Understanding the gaps explains most of the alternatives.
No dates. ISO 8601 strings — "2026-09-06T10:30:00Z" — are the universal convention and are still just strings.
No binary. Base64 in a string, at a 33% size cost.
No schema. JSON Schema is a separate specification that describes the expected shape and validates against it.
No references or circular structures. A cycle cannot be serialised, and attempting it throws.
No integer type. Numbers are numbers, which is why the precision problem above exists.
Comparison: YAML adds comments, references and readability at the cost of ambiguous parsing rules. TOML is designed for configuration and is clearer than both for that purpose. Protocol Buffers and MessagePack are binary, faster and much smaller, and unreadable without tooling. JSON persists because it is universally supported and needs nothing installed.
JSON in the real world
A few patterns come up constantly and are worth having settled opinions about.
Null against absent. {"a": null} and {} are different. The first says the
key exists with no value; the second says nothing about it. APIs that treat them
as equivalent produce ambiguity when a client wants to clear a field rather than
leave it unchanged — which is exactly what a PATCH request needs to express.
Empty array against absent. Same distinction, same trap.
Key ordering is not guaranteed by the standard and is preserved by most parsers in practice. Do not depend on it, and do not shuffle it either — a diff of two JSON files is much more useful when the ordering is stable.
Dates are strings, and ISO 8601 with an explicit offset is the only convention
worth using: "2026-09-06T10:30:00+05:30". A date without a zone is ambiguous, and
a Unix timestamp is unreadable in a file a person has to debug.
Very deep nesting is legal and unpleasant. Beyond four or five levels, most structures are easier to read flattened with references than nested.
Debugging a parse failure quickly
A short procedure that resolves most failures faster than reading the file.
Check that it is JSON at all. An error at position 0 usually means an HTML error page. Look at the first character.
Paste it into a formatter. The formatter fails at the same place the parser does and usually points at it more clearly.
Bisect a large file. Cut it in half, test each half, and repeat. Six or seven rounds isolates a problem in a very large document.
Look for the four rules. Comments, trailing commas, single quotes, unquoted keys — in that order of likelihood.
Check for a byte order mark. An invisible U+FEFF at the start of a file makes parsing fail at position 0 with no visible cause. Files saved from certain Windows editors carry one.
Check the encoding. JSON must be UTF-8. A file saved in a legacy codepage containing accented characters will fail on those bytes and nowhere else, which makes the error position look arbitrary.
What this tool assumes
- Strict RFC 8259 parsing: no comments, no trailing commas, no single quotes, no unquoted keys.
- Formatting uses two-space indentation with keys in their original order.
- Numbers are validated for JSON syntax but not for precision loss on parse — check large integers yourself.
- Duplicate keys are technically permitted by the standard and behave inconsistently across parsers; they are flagged rather than silently accepted.
- Everything runs in your browser. Nothing you paste is sent anywhere, which matters if the JSON contains credentials.