Why your JSON won't parse, and how to find the character

2026-08-29·Developer reference·3 min read·by Sourabh Singh

Why your JSON won't parse, and how to find the character

The five things that make JSON invalid, how to read a parser error into a line and column, and what JSON does not allow that every other format does.

Why your JSON won't parse, and how to find the character

Almost every JSON parse failure is one of five things: a trailing comma, single quotes, unquoted keys, a literal newline inside a string, or comments. JSON allows none of them. The reported error position is usually one token after the actual mistake.

JSON has one of the smallest grammars of any data format, which is exactly why it fails in ways that look absurd. Almost every parse error is one of five things.

The five reasons JSON fails

1. A trailing comma

{ "name": "RankCert", "verified": true, }

Legal in JavaScript object literals, legal in JSON5, legal in most config formats. Not legal in JSON. This is by far the most common cause, because every developer's fingers are trained by JavaScript.

2. Single quotes

{ 'name': 'RankCert' }

JSON strings are double-quoted. Always. Both keys and values. There is no exception.

3. Unquoted keys

{ name: "RankCert" }

Valid JavaScript, invalid JSON. Keys are strings, so they take quotes.

4. A literal newline inside a string

{ "note": "line one
line two" }

Control characters below U+0020 must be escaped. Write \n, not an actual line break.

5. Comments

{
  // the product name
  "name": "RankCert"
}

JSON has no comment syntax. Neither // nor /* */. If you need commented config, you need JSONC, YAML or TOML - not JSON.

Free toolJSON FormatterFormat, validate and minify JSON in your browser. Get the exact line and column of a syntax error instead of a vague parse failure.

Reading the error message

JSON.parse reports a character offset, not a line number, which is useless on a 4,000-character payload. Convert it:

try {
  JSON.parse(source);
} catch (error) {
  const position = Number(String(error.message).match(/position (\d+)/)?.[1]);
  const before = source.slice(0, position);
  const line = before.split("\n").length;
  const column = position - before.lastIndexOf("\n");
  console.error(`${error.message} - line ${line}, column ${column}`);
}

Note that the reported position is usually where the parser gave up, which is one token after the actual mistake. A trailing comma on line 12 typically reports at the closing brace on line 13. Look at the line before the one you are given.

Things JSON allows that surprise people

Duplicate keys are legal. The spec does not forbid them; it leaves the behaviour to the implementation. JSON.parse keeps the last one silently. If your API accepts JSON from clients, a duplicate key is a real attack surface - two parsers in your stack may disagree about which value won.

Top-level scalars are valid. 42, "hello" and null are each a complete, valid JSON document. Not every parser written before 2014 agrees.

Key order is preserved in practice. The spec says objects are unordered, but every mainstream parser preserves insertion order. Do not rely on it across languages.

Numbers have no defined precision. JSON.parse produces IEEE 754 doubles, so any integer above 2^53 loses precision silently. A 64-bit database ID sent as a JSON number will come back wrong. Send large IDs as strings.

Things JSON does not allow that you might expect

  • NaN, Infinity, -Infinity - not valid. JSON.stringify converts them to null without warning.
  • undefined - dropped entirely from objects, converted to null inside arrays.
  • Dates - there is no date type. JSON.stringify(new Date()) produces an ISO string, and parsing gives you a string back, not a Date.
  • BigInt - throws on stringify. Convert to a string first.
  • Trailing decimal points - 1. is invalid; write 1.0.
  • Leading zeros - 007 is invalid.

Minifying, and when it matters

Whitespace typically accounts for 15–30% of a formatted JSON payload. For anything you transmit, minify it and let gzip do the rest - though note that gzip already handles repeated whitespace well, so the real-world saving after compression is smaller than the byte count suggests.

Where minifying genuinely matters is anywhere the payload is stored uncompressed at scale: a jsonb column, a log line, a cache entry.

For anything a human reads - config files, fixtures, API examples - format with two spaces and never think about it again.

Validating structure, not just syntax

A document can be perfectly valid JSON and still be wrong for your use. Syntax validation says the braces match; it says nothing about whether email is present or age is a number.

For that you want a schema. JSON Schema is the portable option; in TypeScript, a runtime validator like Zod gives you both the check and the inferred type from one declaration. Validate at every boundary where data enters your system, because a parser will happily hand you {} when you expected a user.

Launch it where the numbers are checked

RankCert ranks products on domain control we verify ourselves. Listing is free and the link stays dofollow whether or not you display the badge.

Submit a product - free

Tools from this guide