How to fix JSON parse errors

Every common parser message, translated into what is actually wrong — and what to change.

JSON parse errors are almost never subtle. The format is small enough that there are perhaps a dozen ways to get it wrong, and nearly all of them come from writing JavaScript, Python or YAML by accident. Here is each one, and its fix.

Trailing comma

The most common JSON error there is, because JavaScript, TypeScript, Python and most linters all encourage it.

{
  "name": "devbox",
  "version": "1.0",     ← the comma has nothing after it
}

Message: Unexpected token }, or Expected double-quoted property name.

Fix: remove the comma. If you control the consumer, JSON5 and JSONC both allow it — but standard JSON never will.

Single quotes

{'name': 'devbox'}     ← not JSON
{"name": "devbox"}     ← JSON

Message: Unexpected token '.

Fix: double quotes, everywhere, for both keys and string values. This error usually means the text is a JavaScript object literal or a Python dict that was printed rather than serialised. In Python, use json.dumps(), not str() or an f-string.

Unquoted keys

{name: "devbox"}       ← valid JavaScript, invalid JSON
{"name": "devbox"}     ← JSON

Message: Expected property name or } in JSON.

Fix: quote every key. JSON has no bare identifiers.

Comments

{
  // the service name          ← rejected
  "name": "devbox"
}

Message: Unexpected token /.

Fix: remove them. If you need annotation in a config file, either use a format that supports it — JSONC, JSON5, YAML, TOML — or add a "_comment" property, which is ugly but portable.

Python and JavaScript literals

Not JSONUse instead
True / Falsetrue / false — lowercase
Nonenull
undefinednull, or omit the key entirely
NaN, Infinitynull or a string — JSON has no way to express them
{'a': 1} (a Python dict repr)json.dumps() rather than str()

NaN and Infinity are worth a second look: if they appear, some upstream computation divided by zero. The JSON error is a symptom, and the fix belongs where the number was produced.

Unescaped backslashes

{"path": "C:\Users\ada"}       ← \U is not a valid escape
{"path": "C:\\Users\\ada"}     ← correct
{"path": "C:/Users/ada"}       ← also correct, and easier to read

Message: Bad escaped character or Invalid escape.

Fix: double every backslash. JSON allows only \" \\ \/ \b \f \n \r \t and \uXXXX. This bites hardest with Windows paths and with regular expressions stored in config files.

Line breaks inside a string

{"note": "first line
second line"}                  ← rejected

{"note": "first line\nsecond line"}   ← correct

JSON strings cannot span lines. Every newline inside a string must be written as \n.

Unexpected end of input

Something is still open. Usually a truncated paste, a missing closing brace, or — very commonly — JSON.parse("") on an empty HTTP response body:

// The 204 No Content case, which has no body at all.
const text = await response.text();
const data = text ? JSON.parse(text) : null;

Unexpected token < at position 0

You are parsing HTML. The server returned an error page, a login redirect, or a proxy notice, and your code assumed JSON:

const response = await fetch(url);

if (!response.ok) {
  throw new Error(`${response.status} ${response.statusText}`);
}

const type = response.headers.get('content-type') ?? '';
if (!type.includes('application/json')) {
  throw new Error(`Expected JSON, got ${type}: ${(await response.text()).slice(0, 100)}`);
}

return response.json();

Logging the first hundred characters of the body turns a mystifying parse error into an obvious one.

Numbers

Not JSONWhyFix
01234Leading zeros are forbidden1234, or quote it if it is a zip code
.5Must start with a digit0.5
1.Must have a digit after the point1.0
+5A leading plus is not allowed5
0x1FNo hexadecimal literals31

Byte-order marks

A file saved as “UTF-8 with BOM” starts with an invisible U+FEFF, and most parsers reject it. The symptom is an error at position 0 in a file that looks perfect:

# see it
head -c 3 file.json | xxd      # efbbbf means a BOM is present

# strip it
sed -i '1s/^\xEF\xBB\xBF//' file.json

Better: configure your editor to save UTF-8 without a BOM. It is a setting in every editor and it prevents the whole class of problem.

More than one document in the file

{"event": "start"}
{"event": "stop"}

Each line is valid JSON; the file is not. This is NDJSON — JSON Lines — a common log format. Parse it line by line:

const records = text
  .split('\n')
  .filter((line) => line.trim() !== '')
  .map((line) => JSON.parse(line));

Finding it fast

  1. Paste the document into the JSON formatter. It reports the line, the column and the specific cause, and nothing is uploaded — which matters when the document is a production payload.
  2. On the command line: jq empty file.json prints nothing on success and reports the position on failure.
  3. If the document is generated, fix the generator rather than the output. A trailing comma in a template will come back next week.
  4. Never build JSON with string concatenation. Serialise a real data structure — JSON.stringify, json.dumps, Jackson — and the entire class of error disappears.

Frequently asked questions

What does "Unexpected token } in JSON at position 42" mean?

The parser reached a closing brace where it needed something else — almost always because of a trailing comma before it. Position 42 is a character offset from the start of the document, which is why it is so unhelpful in anything larger than a few lines.

What does "Unexpected end of JSON input" mean?

The document ended while a structure was still open — a missing closing brace or bracket, or a truncated copy-paste. It also happens when JSON.parse receives an empty string, which is the usual cause when the value came from an empty HTTP response body.

Why does "Unexpected token < in JSON at position 0" happen?

You are parsing HTML, not JSON. The response was almost certainly an error page — a 404, a 500, or a login redirect — served as HTML while your code assumed JSON. Check the response status and content-type before parsing, and log the first hundred characters of the body when it fails.

Can JSON have comments?

No. Any conforming parser rejects both // and /* */. Some ecosystems use JSONC (VS Code settings files) or JSON5, which do allow them, but a file with comments is not portable JSON. If it must be consumed generally, move the explanation into a "_comment" property or into documentation.

Why did my large ID number change value?

JavaScript parses JSON numbers as IEEE 754 doubles, so integers above 9007199254740991 lose precision — Twitter IDs, Snowflake IDs and database bigints are all in this range. This is not a parser bug and every JavaScript-based tool has it. Transmit large identifiers as strings.

Found a mistake on this page? Tell me — a page that is confidently wrong is worse than no page.