artisan.dev

Fixing "Unexpected token" and Other JSON Parse Errors

By Tahsin Abrar · Updated

JSON is strict. A single trailing comma, a pair of single quotes or an HTML error page where you expected an API response is enough to make JSON.parse() throw — and the error messages are terse. This guide maps the messages you are likely to see in browsers, Node.js and Python to their real causes and fixes.

The quickest way to find the problem is usually to paste the text into the JSON Formatter & Validator, which points to the line and column where parsing failed.

Try itOpen the JSON Formatter & Validator — runs in your browser

Reading the error message

Each JavaScript engine words errors differently, but they all report what the parser found and where. A position is a zero-based character offset from the start of the string; newer engines add a line and column.

EngineTypical message
Chrome / Node.js (V8)Unexpected token '<', "<!DOCTYPE "... is not valid JSON
Chrome / Node.js (V8)Expected double-quoted property name in JSON at position 17 (line 2 column 3)
FirefoxJSON.parse: expected property name or '}' at line 2 column 3 of the JSON data
SafariJSON Parse error: Unexpected identifier "undefined"
Pythonjson.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

The common causes, one by one

Unexpected token '<' — you got HTML, not JSON

If the input starts with <, the server returned an HTML page: a 404 or 500 error page, a login redirect, or your SPA's index.html served for an unknown API path. The bug is in the request, not the JSON. Check response.ok and the Content-Type header before calling response.json(), and log await response.text() to see what actually came back.

const res = await fetch("/api/users");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const type = res.headers.get("content-type") ?? "";
if (!type.includes("application/json")) {
  throw new Error(`Expected JSON, got ${type}: ${(await res.text()).slice(0, 100)}`);
}
const data = await res.json();

Unexpected end of JSON input — the text is empty or cut off

The parser ran out of characters before the value was complete. Usual suspects: an empty response body (a 204 No Content, or an endpoint that returns nothing on success), a missing closing } or ], or a payload truncated by a log line length limit or a database column size. JSON.parse("") throws this error too.

Expected double-quoted property name — single quotes, unquoted keys or a trailing comma

JSON keys must be wrapped in double quotes. JavaScript object literal syntax is not JSON:

{ name: 'Ada', }      // ✗ unquoted key, single quotes, trailing comma
{ "name": "Ada" }    // ✓ valid JSON

A trailing comma after the last property also produces this message, because after a comma the parser expects another property name and finds } instead.

Unexpected token u / "undefined" is not valid JSON

You passed the value undefined — for example JSON.parse(body.data) where data does not exist. JSON.parse converts its argument to a string first, so it ends up parsing the text "undefined". (JSON.parse(null), by contrast, quietly returns null.) Check that the value is a non-empty string before parsing.

Unexpected token / bad control character in string

Strings may not contain raw line breaks or tab characters; they must be escaped as \n and \t. This shows up when JSON is assembled by string concatenation instead of a serializer. Always build JSON with JSON.stringify() (or your language's equivalent), and use the JSON escape tool to escape a value by hand.

Comments, NaN, Infinity and other JavaScript-isms

Standard JSON has no comments, no NaN or Infinity, no hex numbers and no leading + or leading zeros. Config formats such as JSONC (used by tsconfig.json and VS Code settings) and JSON5 allow some of these, but JSON.parse does not. Strip comments with a JSONC-aware parser, or convert the file to YAML if humans need to annotate it.

An invisible byte-order mark

Files saved as "UTF-8 with BOM" begin with the invisible character U+FEFF, which makes the parser fail at position 0 even though the file looks fine. Remove it with text.replace(/^\uFEFF/, "") or re-save the file as plain UTF-8.

Double-encoded JSON

If parsing succeeds but returns a string that itself looks like JSON ("{\"id\":1}"), the data was stringified twice. Fix the producer so it serializes once; as a stop-gap, parse twice.

Errors that don't throw: big numbers

JavaScript numbers are 64-bit floats, so integers above 2^53 − 1 (9,007,199,254,740,991) lose precision silently: JSON.parse('{"id": 12345678901234567890}') gives 12345678901234567000. IDs from systems like Twitter/X or databases with 64-bit keys should be sent as strings. In modern engines you can recover the original text with the context.source argument of a JSON.parse reviver.

A debugging checklist

  1. Log the raw text (first 200 characters and its length) before parsing.
  2. Check the HTTP status and Content-Type — is it really JSON?
  3. Paste it into the JSON validator to get the exact line and column.
  4. Look at the character before the reported position — that is often where the mistake is (a missing comma or quote).
  5. Fix the producer so it uses a real serializer instead of string concatenation.

FAQ

Does JSON allow trailing commas?
No. Standard JSON (RFC 8259) forbids a comma after the last element of an object or array. JSON5 and JSONC allow them, but JSON.parse does not.
Can JSON use single quotes?
No. Both keys and string values must use double quotes. Single quotes are valid in JavaScript object literals, not in JSON.
Why does JSON.parse fail at position 0?
The very first character is invalid — usually an HTML page (<), an empty string, the word undefined, or an invisible byte-order mark at the start of a file.

More guides