Fix "SyntaxError: Unexpected token in JSON" — All Causes
The SyntaxError: Unexpected token X in JSON at position N message is the most common JSON error in JavaScript, Node.js, and browser applications. It means the JSON parser hit a character it did not expect at a specific position in the string. The frustrating part is that the position reported is often where the parser gave up — not where the actual mistake is. This guide covers every cause of this error with exact fixes.
How to Read the Error Message
The error format is: SyntaxError: Unexpected token [CHAR] in JSON at position [N]. The character tells you what the parser found. The position is the character index from the start of the string. Some environments (Node.js 20+, modern browsers) also give a line and column number which is more useful.
| Token in error | Most likely cause |
|---|---|
< at position 0 | Server returned HTML, not JSON (see below) |
} or ] | Trailing comma before this closing bracket |
' (single quote) | Used single quotes instead of double quotes |
/ at position 0–3 | Comment in JSON (// not allowed) |
u or n | undefined or unquoted value |
| A letter at position 0 | Unquoted key or non-JSON preamble in response |
Error 1: Server Returned HTML Instead of JSON
This is the #1 cause of Unexpected token < at position 0. Your fetch or XMLHttpRequest called an endpoint, but the server returned an HTML page instead of JSON. The < is the first character of <!DOCTYPE html> or an HTML error page. The JSON parser chokes immediately.
Causes: wrong URL (hitting a 404 page), server crash (500 error page returned), auth failure (redirect to a login page), or CORS error (browser received an HTML CORS rejection).
// WRONG — assumes response is always JSON
const data = await fetch('/api/users').then(r => r.json());
// RIGHT — check status and content type first
const res = await fetch('/api/users');
if (!res.ok) {
const text = await res.text();
throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
}
const contentType = res.headers.get('content-type') || '';
if (!contentType.includes('application/json')) {
throw new Error(`Expected JSON, got: ${contentType}`);
}
const data = await res.json();
Always read the raw response text when debugging this error. await res.text() shows you exactly what the server sent, not what you expected it to send.
Error 2: Trailing Comma
JSON does not allow a comma after the last item in an object or array. JavaScript does — which is exactly why developers write trailing commas by habit.
// BROKEN
{"name": "Maria", "role": "admin",}
["one", "two", "three",]
// FIXED
{"name": "Maria", "role": "admin"}
["one", "two", "three"]
The error points to the closing } or ], not the comma. Always check the character immediately before the closing bracket when you see this error.
Error 3: Single Quotes
JSON requires double quotes for all strings — both keys and values. Single quotes are valid JavaScript but invalid JSON. This is the most common error when copying JavaScript object literals into a JSON context.
// BROKEN
{'name': 'Maria', 'role': 'admin'}
// FIXED
{"name": "Maria", "role": "admin"}
Error 4: Unquoted Keys
In JavaScript object literals, keys can be unquoted if they are valid identifiers. JSON requires all keys to be double-quoted strings.
// BROKEN (valid JavaScript object, invalid JSON)
{name: "Maria", role: "admin"}
// FIXED
{"name": "Maria", "role": "admin"}
Error 5: Comments
JSON does not support comments — not // line comments, not /* */ block comments. This error appears most often in configuration files where developers naturally want to document their settings.
// BROKEN
{
// User settings
"theme": "dark",
"language": "en" /* default */
}
// FIXED — remove comments, or use "_comment" key
{
"_comment": "User settings",
"theme": "dark",
"language": "en"
}
If you need comments in your config files, switch to YAML, TOML, or JSONC (JSON with Comments — supported by VS Code).
Error 6: Undefined and Non-JSON Values
JSON has six types: string, number, boolean, null, object, array. undefined, NaN, Infinity, functions, and Date objects are not valid JSON values. If you stringify them and then parse, you get this error.
// This silently produces invalid JSON
JSON.stringify({a: undefined, b: NaN, c: Infinity});
// Result: "{}" — undefined and NaN/Infinity become nothing or null
// Safe pattern
const safe = {
value: Number.isFinite(x) ? x : null,
data: item ?? null // undefined → null
};
Error 7: Parsing an Empty String
JSON.parse(""), JSON.parse(null), and JSON.parse(undefined) all throw this error. This happens when an API returns an empty body (like a 204 No Content) and you try to parse it.
// Guard against empty responses
function safeParse(text) {
if (!text || !text.trim()) return null;
try {
return JSON.parse(text);
} catch (err) {
console.error('JSON parse error:', err.message, 'Input:', text.slice(0, 100));
return null;
}
}
Error 8: BOM or Invisible Characters
A Byte Order Mark (BOM) is an invisible character () sometimes added by Windows text editors at the start of a file. It makes JSON appear to start with an invisible character before the {, causing Unexpected token at position 0.
// Strip BOM before parsing const jsonString = rawString.replace(/^/, ''); const data = JSON.parse(jsonString);
Error 9: Concatenated JSON Objects
Sometimes log files or streaming sources contain multiple JSON objects concatenated without any separator — this is called NDJSON (Newline Delimited JSON) or JSON Lines. JSON.parse cannot handle more than one root object.
// BROKEN — two JSON objects, not valid JSON
{"id":1,"name":"Maria"}{"id":2,"name":"Carlos"}
// FIXED — parse each line separately (NDJSON)
const objects = text
.split('
')
.filter(line => line.trim())
.map(line => JSON.parse(line));
Debugging Strategy
When you hit this error: first, log the raw string before parsing — console.log(JSON.stringify(theString)) shows escape sequences and invisible characters. Second, paste the value into ToolPry's JSON Formatter, which shows the exact error location highlighted in the source. Third, check the error position and look at the character immediately before it — that is almost always where the real problem is, not the position itself.
Wrap all JSON.parse calls in try/catch in production code. A JSON parse error should never crash your application — it should be caught, logged with the raw input, and handled gracefully with a fallback.