JSON.parse() is strict on purpose. It does not parse "almost JSON", JavaScript object literals, JSONC config, HTML error pages, or a value that used to be an object before something turned it into "[object Object]".
An "Unexpected token" error means the parser reached a character that cannot appear at that exact point in the JSON grammar. The token tells you what the parser actually received. The position, line, or column tells you where to look. Debug those two facts first, before changing the parser or guessing at the API.
Read the Token First
Use the first unexpected token as a shortcut. In real projects, this table gets you to the right bug faster than reading the whole payload.
| Token or message | What usually happened | First place to check |
|---|---|---|
< |
The response is HTML, not JSON. Usually a 404 page, login redirect, proxy error, or SPA fallback route. | Network tab, status code, content-type, request URL, auth cookies. See Unexpected token < in JSON. |
u at position 0 |
You passed undefined or an unset value. |
Function arguments, state initialization, environment variables, optional cache reads. See Unexpected token u. |
o at position 1 |
A plain object was coerced to "[object Object]". |
Code that calls JSON.parse() on an object or sends body: payload instead of JSON.stringify(payload). See Unexpected token o. |
' |
The input uses single quotes. | Copied JavaScript object literal, Python str(dict), hand-edited config. See single quote repair. |
} or ] after a comma |
The input has a trailing comma. | Last item in an object or array. See trailing comma repair. |
/ |
The input contains // or /* */ comments. |
JSONC config pasted into a strict JSON parser. |
T, F, N, or another identifier |
The input uses True, False, None, NaN, Infinity, undefined, or another non-JSON literal. |
Python output, JavaScript debug output, LLM-generated snippets. See LLM JSON repair. |
| Invisible character at position 0 | A byte-order mark or control byte is before the first visible character. | File encoding, copied text, CLI output. |
| Unexpected end of JSON input | The parser ran out of text before the object, array, or string closed. | Empty response, truncated stream, missing bracket. See Unexpected end of JSON input. |
| Non-whitespace after JSON data | There is extra text after a complete JSON value. | Logs pasted with JSON, two JSON documents concatenated, NDJSON parsed as one value. |
Different engines phrase the error differently:
SyntaxError: Unexpected token '<', "<html>..." is not valid JSON
SyntaxError: Unexpected token u in JSON at position 0
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
SyntaxError: JSON Parse error: Single quotes (') are not allowed in JSON
Do not overfit to the exact wording. Chrome, Node.js, Firefox, and Safari do not use identical messages. Treat the message as a clue, then inspect the input.
Log What You Actually Parsed
Most wasted JSON debugging time comes from assuming the input is the value you meant to parse. Log a safe preview before you repair anything.
function previewForJsonParse(value) {
const text = typeof value === 'string' ? value : String(value);
return {
type: typeof value,
length: text.length,
firstChar: JSON.stringify(text[0] ?? ''),
preview: JSON.stringify(text.slice(0, 160)),
};
}
console.log(previewForJsonParse(raw));
That preview catches several problems immediately:
type: "undefined"means you never had JSON text.preview: "\"[object Object]\""means an object was coerced before parsing.preview: "\"<!DOCTYPE html>...\""means your API returned HTML.firstChar: "\"\\ufeff\""means a BOM is at position 0.length: 0means you are parsing an empty string, not a malformed object.
For production code, return a parse result instead of letting a random component crash:
function safeJsonParse(text) {
try {
return { ok: true, value: JSON.parse(text) };
} catch (error) {
return {
ok: false,
error: error instanceof SyntaxError ? error.message : String(error),
};
}
}
Use this pattern at the edge of your app: form imports, local storage, feature flags, clipboard paste, and developer tools. For internal API contracts, it is usually better to fail loudly and fix the producer.
If the Token Is <, Your Fetch Returned HTML
Unexpected token '<' is the classic fetch failure. The body starts with < because the server returned an HTML document:
/api/useris misspelled and returned the app shell.- The user is logged out and got a login page.
- A reverse proxy returned a branded 502 page.
- A serverless route crashed and returned an HTML error page.
- A static host rewrote the API path to
index.html.
This helper reads the body once, checks the status, checks the content type, and includes a small preview in the error:
async function readJsonResponse(response) {
const text = await response.text();
const contentType = response.headers.get('content-type') || '';
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${text.slice(0, 200)}`);
}
if (!contentType.includes('application/json')) {
throw new Error(
`Expected JSON, got ${contentType || 'no content-type'}: ${text.slice(0, 200)}`,
);
}
try {
return JSON.parse(text);
} catch (error) {
throw new Error(
`Invalid JSON response: ${error.message}. Body starts with ${JSON.stringify(
text.slice(0, 120),
)}`,
);
}
}
Do not call await response.json() first when you are debugging this case. Once the stream is consumed, you lose the raw body unless you clone the response. Read text, inspect it, then parse.
If the Token Is u, You Parsed undefined
JSON.parse(undefined) does not receive a special JavaScript value. The algorithm converts the argument to a string first, so the parser sees "undefined" and rejects the u.
JSON.parse(undefined);
// SyntaxError: Unexpected token u in JSON at position 0
Common causes:
- A function argument was optional, but the parser assumes it is present.
- A React/Vue/Svelte component parses data before async loading completes.
- An environment variable is missing.
- A cache wrapper returns
undefinedfor a miss. - A property path is wrong:
settings.userJsoninstead ofsettings.user.json.
Guard the source, not the parser:
function readConfig(raw) {
if (typeof raw !== 'string' || raw.trim() === '') {
return { theme: 'system', compact: false };
}
return JSON.parse(raw);
}
const result = readConfig(process.env.APP_SETTINGS_JSON);
One detail that surprises people: JSON.parse(null) returns null. That is because null is converted to the string "null", and "null" is valid JSON. undefined is not.
If the Token Is o, You Parsed an Object
JSON.parse() expects text. If you pass a plain object, JavaScript coerces it to "[object Object]". The parser accepts the first [ as the start of an array, then rejects the o at position 1.
const payload = { name: 'Ada' };
JSON.parse(payload);
// SyntaxError: Unexpected token o in JSON at position 1
Fix it by deleting the parse when you already have an object:
const payload = { name: 'Ada' };
// Already an object. Use it directly.
console.log(payload.name);
For requests, serialize before sending:
await fetch('/api/profile', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ name: 'Ada' }),
});
If your log contains the literal text "[object Object]", a repair tool cannot recover the original keys and values. The data was lost earlier. Fix the code path that produced the string. The related article Fix "[object Object] is not valid JSON" walks through that case in detail.
If the Token Is a Quote, Comma, or Slash, You Have "Almost JSON"
Many unexpected token errors come from syntax that is valid somewhere else, just not in strict JSON.
Single quotes are valid in JavaScript strings, not JSON strings:
JSON.parse("{'name': 'Ada'}");
// SyntaxError
JSON.parse('{"name": "Ada"}');
// OK
Unquoted keys are valid in JavaScript object literals, not JSON:
JSON.parse('{name: "Ada"}');
// SyntaxError
JSON.parse('{"name": "Ada"}');
// OK
Trailing commas are valid in modern JavaScript and many config formats, not JSON:
JSON.parse('{"name": "Ada", "score": 98,}');
// SyntaxError near the closing brace
JSON.parse('{"name": "Ada", "score": 98}');
// OK
Comments are valid in JSONC, not JSON:
JSON.parse(`{
// internal note
"name": "Ada"
}`);
// SyntaxError near /
Avoid quick regex comment strippers unless they are string-aware. This breaks real data:
const raw = '{"url": "https://example.com/a//b"}';
// Bad idea: this removes part of the string value.
raw.replace(/\/\/.*$/gm, '');
For config files, use a JSONC parser intentionally. For pasted or one-off input, repair it, review the output, then validate it as strict JSON.
If the Token Is T, F, N, I, or Another Identifier
Strict JSON has only three literal words: true, false, and null. They are lowercase.
These are not JSON:
JSON.parse('{"enabled": True}');
JSON.parse('{"enabled": False}');
JSON.parse('{"value": None}');
JSON.parse('{"value": undefined}');
JSON.parse('{"ratio": NaN}');
JSON.parse('{"limit": Infinity}');
Fix the producer where possible:
- In Python, use
json.dumps(data), notstr(data). - In JavaScript, use
JSON.stringify(data), not template strings or object interpolation. - For LLM output, ask for strict JSON and still validate the response.
- For
NaNandInfinity, decide whether the value should becomenull, a string sentinel, or a domain-specific error.
JSON.stringify() converts NaN, Infinity, and -Infinity to null in object values. That can be acceptable for charts, but it is a lossy decision. Do not let it happen silently in finance, permissions, quotas, or audit logs.
If Position 0 Looks Fine, Check Invisible Characters
Sometimes the first visible character is {, but the parser reports an unexpected token at position 0. Inspect the character code instead of trusting your eyes.
function inspectFirstCharacter(text) {
const char = text[0] ?? '';
return {
visible: JSON.stringify(char),
codePoint: char ? `U+${char.codePointAt(0).toString(16).toUpperCase()}` : null,
};
}
A UTF-8 byte-order mark appears as U+FEFF. Strip it only at the start of the document:
const clean = raw.replace(/^\uFEFF/, '');
const data = JSON.parse(clean);
Do not remove every control character blindly. Tabs, newlines, and carriage returns are legal whitespace between JSON tokens, and escaped versions such as \n can be meaningful inside strings. If the problem is a raw newline, NUL byte, ANSI color sequence, or another control character inside a string, use the separate bad control character guide.
Map a Position to Line and Column
V8-style errors usually include position 123. That position is a zero-based character index into the string. Convert it to line and column before you hand the bug to another person.
function describeJsonParseError(text, message) {
const match = String(message).match(/position (\d+)/);
if (!match) return null;
const position = Number(match[1]);
const before = text.slice(0, position);
const line = before.split('\n').length;
const column = before.length - before.lastIndexOf('\n');
return {
position,
line,
column,
character: JSON.stringify(text[position] ?? ''),
context: JSON.stringify(text.slice(Math.max(0, position - 30), position + 30)),
};
}
Use JSON.stringify() around the character and context when logging. It makes quote characters, tabs, newlines, BOMs, and other invisible details visible.
Decide Whether to Repair or Reject
Not every unexpected token should be repaired. A JSON repair step is helpful when the input is human-produced or exploratory. It is dangerous when the input represents a contract, permission, payment, or destructive action.
| Situation | Good response |
|---|---|
| A user pasted approximate JSON into a developer tool | Repair, format, then show the result for review. |
| An LLM returned almost-JSON | Repair only as a cleanup step, then validate required keys and types. |
| Your own API returned malformed JSON | Reject the response and fix the server or serializer. |
| Fetch returned HTML | Fix the URL, auth, route, proxy, or content type. Do not repair HTML into JSON. |
| A repository config file has comments or trailing commas | Use JSONC intentionally, or enforce strict JSON in CI with jq empty file.json. |
| A financial, permission, migration, or deletion workflow receives invalid JSON | Reject it, log the raw preview, and require a valid producer. |
That distinction is what keeps a repair tool useful without hiding upstream bugs.
Use JSON Fix for Pasted Input
If you have a broken JSON sample and need a clean version quickly, paste it into JSON Fix. It can repair common syntax issues such as single quotes, trailing commas, unquoted keys, comments, Python literals, undefined, markdown fences, and unclosed structures. The repair runs in your browser, so the text does not need to leave your machine.
After repair, do one more pass:
- Format the output and confirm the shape.
- Compare before and after with JSON Diff if the data matters.
- Validate the final text with strict JSON parsing.
- Use schema validation for API payloads, imports, and configuration.
Frequently Asked Questions
What does "Unexpected token in JSON" mean?
JSON.parse() found a character that is not valid at that point in the JSON grammar. The named token tells you what the parser actually received, and the position tells you where to inspect the input.
Why does "Unexpected token <" happen with fetch?
The response body starts with HTML, usually a 404 page, login redirect, proxy error, or front-end fallback route. Read the response as text, check response.ok and content-type, and fix the endpoint before parsing.
How do I fix "Unexpected token u in JSON at position 0"?
You passed undefined or an unset value to JSON.parse(). Guard the value before parsing and initialize state, function arguments, localStorage fallbacks, or environment variables deliberately.
Why does JSON.parse({}) cause token o or [object Object]?
JSON.parse() expects text. A plain object is coerced to the string [object Object], so the parser rejects the o in object. Use the object directly, or call JSON.stringify() when you need JSON text.
How do I find the exact position of the error?
If the engine reports a position, slice the string around that index and map it to line and column. Stringify the snippet when logging so invisible characters and quote characters become visible.
Can JSON.parse() handle comments or trailing commas?
No. Comments and trailing commas are valid in some JavaScript and JSONC contexts, but not strict JSON. Remove them, use a JSONC parser for config files, or repair the input before strict parsing.
Should I auto-repair unexpected token errors?
Repair is fine for pasted examples, developer tools, and LLM output after review. For API contracts, financial actions, permission changes, or destructive workflows, reject malformed JSON and fix the producer.
Why do Chrome, Firefox, and Safari show different JSON.parse errors?
They use different JavaScript engines and message wording. Treat the exact text as a clue, but debug from the actual token, input preview, line or column, and whether the payload is truly JSON.
Related Fixes
- Unexpected token
<in JSON at position 0 - Unexpected token
uin JSON at position 0 - Unexpected token
oin JSON at position 1 - Fix "[object Object] is Not Valid JSON"
- Fix JSON Unexpected Token Errors
- Fix trailing commas in JSON
- Fix single quotes in JSON
- Unexpected end of JSON input
Sources
- RFC 8259 - the JSON Data Interchange Format and grammar.
- MDN: JSON.parse() - JavaScript parser behavior and exceptions.
- ECMA-262 JSON.parse - the language-level algorithm, including input coercion.
- MDN: Response.json() - fetch response parsing behavior.
Last reviewed July 2026.