BrowserTools
Encoders & Converters

12 JSON Syntax Errors and Why Parsers Reject Them

JSON is strict on purpose: it is a wire format between systems that share no runtime, so every ambiguity has to be settled by whoever wrote the file. That turns an ordinary slip — a trailing comma, a single-quoted name, a comment — into a hard parser failure, and one slip, a repeated key, into silent corruption. All twelve are shown broken and then fixed below; to check a file of your own, the JSON Formatter linked from this guide parses it locally.

By Guga — BrowserTools maintainer, Tbilisi, Georgia·Last updated 2026-10-01·6 min read·1270 words

Free and local. It beautifies or minifies with two- or four-space indentation and validates with precise error positions, so a rejected file says where to look instead of only that it failed.

format or validate JSON in your browser →

Why JSON is strict

JSON is a subset of JavaScript expression syntax, but a deliberately smaller one. The grammar in RFC 8259 requires double-quoted names and strings, forbids trailing commas and comments, and requires a number to match a specific pattern with no leading zeros, no trailing decimal point and no hexadecimal form. It has no representation for NaN, Infinity or undefined, none of which are numbers in the grammar. Top-level values can be any JSON value, not only an object or array, so a bare string or number is valid although many consumers assume otherwise. The strictness exists because JSON is a wire format between systems with no shared runtime, and every ambiguity must be resolved by a producer and a consumer written in different languages. The consequence for debugging is that parsers are single-pass and stop at the first violation, and the reported position is where the parser gave up rather than necessarily where the mistake is. A missing brace near the top of a large file surfaces as an error near the bottom.

The 12 mistakes, broken and fixed

One, a trailing comma: {"a":1,} becomes {"a":1}. Two, single-quoted names: {'name':'John'} becomes {"name":"John"}. Three, an unquoted name: {name:"John"} becomes {"name":"John"}. Four, a comment, either // note or a /* block */, which JSON does not define. Five, duplicate keys: {"status":"ok","status":"error"} parses in most engines and the second value silently wins, so this one does not throw, it corrupts. Six, a leading zero: {"zip":0123} is rejected, so write {"zip":"0123"} since a postal code is not a number. Seven, Infinity or NaN, which are rejected; use null and decide what the consumer should do with an absent value. Eight, undefined, which is rejected, so omit the key. Nine, a literal tab or newline inside a string, which must be escaped as \t and \n. Ten, a hexadecimal number: {"c":0xFF} is rejected, use 255. Eleven, a trailing decimal point: {"n":42.} is rejected. Twelve, a byte order mark before the opening brace, which JSON.parse refuses at position zero; save as UTF-8 without a BOM.

Numbers, precision, and round trips

JSON numbers are parsed into IEEE 754 double-precision floats, which represent integers exactly only up to 2^53 − 1, or 9,007,199,254,740,991. A 64-bit database key above that loses precision silently and without an error. Parsing 9876543210987654321 in JavaScript gives 9876543210987655000, and re-serialising produces a number that no longer matches what you sent, so any 64-bit identifier has to travel as a string. Money has the same problem for a different reason: binary floating point cannot represent most decimal fractions exactly, so 0.1 + 0.2 is 0.30000000000000004 and a price accumulated by repeated addition drifts. Store money as an integer count of minor units, 1999 for 19.99, or as a decimal string. Large values carry a formatting hazard too, since JavaScript prints 1e21 for a sufficiently large integer. If a number must survive a round trip exactly through JavaScript, it cannot be a JSON number.

null, missing keys, and undefined

These three look interchangeable and are not. A key that is absent says nothing about whether the producer had a value. A key present with the value null says the producer considered the field and has no value for it. undefined is a JavaScript value JSON cannot represent, so JSON.stringify drops any object property whose value is undefined, changing the shape of the object on the way out. That is a real bug source when you build an object with optional fields: {"a":1,"b":undefined} serialises to {"a":1}. A consumer reading b gets undefined either way, but one checking Object.keys sees a difference. Pick one convention per API. The rule that avoids most of the difficulty: omit keys that do not apply, use null for fields that apply but have no value, and never write the literal string undefined. Converting to CSV flattens this, since a missing key and a null both become an empty cell.

Duplicate keys, escaping, and encoding

RFC 8259 says object names SHOULD be unique and notes that behaviour is unpredictable when they are not. Engines generally keep the last occurrence, so {"role":"user","role":"admin"} parses to one key with the value admin and no warning. If a value can appear twice, you need a lint rule on the producer side, because nothing downstream will catch it. Escaping has its own rules. All control characters from U+0000 to U+001F must be escaped, and a raw tab inside a string literal produces a bad-control-character error. Unicode escapes are legal, and a surrogate pair written as two escapes is valid, though an unpaired escape such as \uD83D parses in JavaScript and yields a lone surrogate rather than an error, so some consumers accept it and others mangle it. Normalise to literal characters or well-formed pairs. Finally, decode as UTF-8. Text encoded as Latin-1 and read as UTF-8 turns é into é, and no parser can detect that for you.

A validation workflow

Validate at the boundary, on every input, not only where something breaks. In Node, wrap the parse: try { JSON.parse(fs.readFileSync('data.json', 'utf8')); } catch (e) { console.error(e.message); process.exit(1); }. In CI, jq empty data.json exits non-zero on invalid input and costs nothing. For a service boundary, set the content type to application/json and make sure the producer serialises with JSON.stringify rather than assembling a string by hand, since template literals are the usual source of the quoting and escaping bugs listed above. The JSON formatter here wraps JSON.parse, returns the parser's own message with line and column, and formats only once the input parses, so a beautified result is valid by construction. Indent options are 2 spaces, 4 spaces or a tab, and the minify view strips unnecessary bytes. For very large files a streaming parser processes input incrementally instead of holding the whole string and object graph in memory at once.

Diffing reliably, and when to pick another format

To make a diff readable, normalise both sides first: indent both to 2 spaces and sort keys alphabetically on both. What remains is a real value change rather than a reordering artefact. Prettier with the json parser and its check flag does this in a pre-commit hook, the cheapest way to stop formatting drift filling a review with noise. When choosing a format, use YAML or JSON5 when humans edit the file, since both support comments, and use strict JSON across a service boundary, where the interoperability guarantee is what you are paying for. CSV has its own sharp edges on the way in, chiefly the quoting rules: a field containing a comma, quote or newline must be wrapped in quotes, and an embedded quote is doubled. A naive split on commas destroys those rows. The CSV to JSON tool here implements the quoted-field rules properly. The reverse direction must choose what to do with nested objects, since CSV has no nesting; the practical approach is to flatten a nested object to a JSON string in its own column.

Frequently Asked Questions

Why does JSON not allow comments?
JSON was designed as a data interchange format, and Douglas Crockford's position was that a parser accepting comments has no way to know what a comment means when it re-serialises. For configuration, use JSONC, JSON5 or YAML, all of which support comments, and strip them before the data crosses a strict parser.
Can I use single quotes in JSON?
Not in strict JSON. Names and string values must use double quotes. Some parsers are lenient, Python's json module for instance will read single-quoted input, which is exactly the problem: code that works locally can fail on a stricter consumer.
What is the correct way to handle dates in JSON?
JSON has no date type. Store an ISO 8601 string such as 2026-09-21T10:00:00.000Z, or a Unix timestamp as a number. The string is readable and carries an explicit timezone; the number is smaller and unambiguous if you state the unit. Do not mix the two in one API.
How do I fix trailing comma errors?
Remove the comma immediately preceding the closing brace or bracket. A formatter configured for JSON will not emit one, so beautifying with strict settings removes the class of error entirely. In the JSON formatter here, pick an indent of 2, 4 or tab and the output is canonical and comma-free.