JSON errors arrive as one terse line, but the causes behind them are a short list: the server sent HTML instead of JSON, there was nothing to parse, or the JSON was written by hand like a JavaScript object.
The Chrome, Node.js and Python messages below come from actually running each input through Chrome 152, Node.js 24 and Python 3.11, so you can match yours word for word.
Read the position first
For {"name": "kim", "age": 20,} (note the trailing comma):
| Environment | Message |
|---|---|
| Chrome, Edge, Node.js | Expected double-quoted property name in JSON at position 26 (line 1 column 27) |
Python json
|
Expecting property name enclosed in double quotes: line 1 column 27 (char 26) |
| Older Chrome / Node.js | Unexpected token } in JSON at position 26 |
position (and Python's char) counts from 0. line and column count from 1, like your editor.
{"name": "kim", "age": 20,}
^ position 25: the comma you actually need to delete
^ position 26: where the parser gave up
The reported spot is where the parser could no longer continue, not where you made the mistake. Start there and read backwards.
Unexpected token '<' — you got HTML, not JSON
Chrome/Edge : SyntaxError: Failed to execute 'json' on 'Response': Unexpected token '<', "<!DOCTYPE "... is not valid JSON
Firefox : SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
Safari : SyntaxError: JSON Parse error: Unrecognized token '<'
The < is the start of <!DOCTYPE html>. Staring at the code that builds your JSON will not help — the server sent a document. Usual suspects: a typo in the API path (404 page), a server crash (500 page), an expired session redirecting to a login page, or a dev server without a proxy returning index.html for /api/....
Open DevTools → Network → the request → Response. If you see HTML, that is your answer. Then stop it from hiding the real cause:
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")) {
const body = await res.text();
throw new Error(`Expected JSON, got: ${body.slice(0, 80)}`);
}
const data = await res.json();
Unexpected end of JSON input — nothing to parse
You parsed an empty string: a 204 No Content response, an empty error body, or a zero-byte file. Python reports this and the HTML case identically as Expecting value: line 1 column 1 (char 0), so print what you received.
Truncated JSON looks different: {"name": "kim", "tags": ["a", "b" gives Expected ',' or ']' after array element in JSON at position 33. If the position is the very end of the input, suspect a cut-off response or a log copied without its tail.
JavaScript syntax is not JSON
| Mistake | Example | Chrome / Node.js | Python |
|---|---|---|---|
| Trailing comma (object) | {"a": 1,} |
Expected double-quoted property name |
Expecting property name enclosed in double quotes |
| Trailing comma (array) | [1, 2, 3,] |
Unexpected token ']' |
Expecting value |
| Single quotes | {'name': 'kim'} |
Expected property name or '}' |
Expecting property name enclosed in double quotes |
| Unquoted key | {name: "kim"} |
Expected property name or '}' |
Expecting property name enclosed in double quotes |
| Comment | {"a": 1 // note} |
Expected ',' or '}' after property value |
Expecting ',' delimiter |
Notice that the message rarely names the mistake. A comment does not say "comments are not allowed". Trust the position more than the wording. (And tsconfig.json accepting comments is why this habit sneaks into package.json — that one is JSONC, this one is not.)
Values JSON doesn't have
NaN, Infinity, undefined, Python's True/None, leading zeros (007) and hex (0x1F) all fail. Unexpected token 'N' covers both NaN and None, so read the quoted fragment after it.
The sneaky one: Python writes NaN by default and reads it back happily.
import json
json.dumps({"score": float("nan")})
# '{"score": NaN}' <- not standard JSON; JavaScript will throw
json.dumps({"score": float("nan")}, allow_nan=False)
# ValueError: Out of range float values are not JSON compliant
Also: str(data) in Python gives {'ok': True, 'v': None} — not JSON. Use json.dumps().
Errors inside strings
Bad control character in string literal in JSON at position 14 <- a real newline or tab inside quotes; write \n
Bad escaped character in JSON at position 13 <- "C:\Users\kim": \U is not an escape
Unexpected token '', "{"a": 1}" is not valid JSON <- looks empty? that's a BOM
For the Windows path, use C:\\Users\\kim or C:/Users/kim. Python's message for the same input points one character earlier (char 12, the backslash) than V8 (position 13, the U).
The BOM case is invisible in most editors. On Windows, PowerShell 5.1 is a common source: Set-Content -Encoding UTF8 adds a BOM, and > or Out-File write UTF-16. Fixes: open(path, encoding="utf-8-sig") in Python, text.replace(/^\uFEFF/, "") in Node.js, or re-save as plain UTF-8.
"undefined" is not valid JSON and "[object Object]"
JSON.parse stringifies whatever you pass it.
-
"undefined" is not valid JSON— oftenlocalStorage.setItem("user", undefined), which stores the literal string"undefined". -
"[object Object]" is not valid JSON— you parsed something already parsed, like axios'sresponse.data. -
Unexpected non-whitespace character after JSON— two JSON values back to back, e.g. a JSON Lines file parsed in one go. Split on newlines first.
Checklist
-
<in the message → check the response, not the JSON. -
end of JSON input→ empty or truncated input. - A position → look just before it.
- Big file, no position →
python -m json.tool file.jsonprintsline 4 column 1-style locations. - Building JSON by string concatenation → switch to
JSON.stringify()/json.dumps().
The full version with diagrams, plus a browser-only formatter that highlights the failing line (nothing is uploaded):
- Guide: https://www.coding-now.com/en/guides/json-parse-errors?utm_source=devto
- JSON formatter & validator: https://www.coding-now.com/en/json-formatter?utm_source=devto
Which of these has cost you the most debugging time?
Top comments (0)