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 browserReading 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.
| Engine | Typical 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) |
| Firefox | JSON.parse: expected property name or '}' at line 2 column 3 of the JSON data |
| Safari | JSON Parse error: Unexpected identifier "undefined" |
| Python | json.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 JSONA 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
- Log the raw text (first 200 characters and its length) before parsing.
- Check the HTTP status and
Content-Type— is it really JSON? - Paste it into the JSON validator to get the exact line and column.
- Look at the character before the reported position — that is often where the mistake is (a missing comma or quote).
- 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.parsedoes 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 wordundefined, or an invisible byte-order mark at the start of a file.
More guides
- How JWT Signatures Work (HS256 vs RS256, Explained)What the three parts of a JSON Web Token are, how the signature is computed, why decoding is not verifying, and the mistakes that lead to JWT vulnerabilities.
- Cron Syntax Cheatsheet: Fields, Operators and 25 Ready-Made SchedulesA practical reference to the five-field cron format, the special characters, common schedules you can copy, and the gotchas in crontab, GitHub Actions and Kubernetes.
- Base64 Encoding Explained: How It Works and When to Use ItHow Base64 turns bytes into text, why output is a third larger, what the = padding means, Base64 vs Base64URL, and how to handle Unicode correctly in JavaScript.