Unexpected Token in JSON at Position 0: Causes and Fixes
Fix Unexpected token errors in JSON by checking the raw response, HTTP status, content type, quotes, commas, empty bodies, and hidden characters.
const raw = "<!doctype html>";
JSON.parse(raw);
The error starts with the input, not the parser
“Unexpected token in JSON at position 0” means the first character of the value is not valid where a JSON value should begin. The fastest fix is to inspect the exact raw string passed to JSON.parse(). Do not retry the same parse or immediately replace characters.
In API code, the input is often not JSON at all. A leading < usually means an HTML error, login, or proxy page was returned. A leading u commonly means undefined reached the parser. An empty or truncated response more often produces an “Unexpected end of JSON input” message.
Use the unexpected character as a diagnostic clue
JavaScript engines vary in their exact error wording, but the reported character is still useful. Check it against the raw input before looking for a missing bracket deeper in the payload.
| Message clue | Likely input | First check |
|---|---|---|
| Unexpected token < at position 0 | HTML or XML response | Status, redirects, and Content-Type |
| Unexpected token u at position 0 | undefined converted to text | The value before JSON.parse() |
| Unexpected token o at position 1 | An object converted to [object Object] | Whether the value is already parsed |
| Unexpected token ' at position 0 or 1 | Single-quoted string or property name | Replace JSON syntax at the source |
| Unexpected token } or ] | Trailing comma or missing value | The item immediately before the token |
| Unexpected end of JSON input | Empty or incomplete body | Body length and upstream connection |
A safe workflow for debugging JSON responses
Preserve the response as text for one diagnostic pass. That lets you inspect the HTTP result and the raw body together. A response can have a valid network connection while still carrying an HTML error page, a plain-text message, or no content.
const response = await fetch(url);
const raw = await response.text();
console.log({
status: response.status,
contentType: response.headers.get("content-type"),
preview: raw.slice(0, 120),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${raw.slice(0, 120)}`);
}
if (!raw.trim()) {
return null; // Decide explicitly how your app handles an empty body.
}
const data = JSON.parse(raw);- Check the status. A 401, 403, 404, 429, or 500 response often has a non-JSON body even when the successful response is documented as JSON.
- Check Content-Type. For a JSON API, expect a media type such as
application/json. A header is a useful signal, but the body must still contain valid JSON. - Print a short escaped preview. Using
JSON.stringify(raw.slice(0, 120))makes newlines, tabs, and some hidden characters visible. - Validate the captured body. If it really is JSON, a validator can identify the structural error without changing the data.
- Fix the producer. Correct the API, template, serializer, or stored value that generated the invalid response.
Fix the JSON grammar, not just the visible character
RFC 8259 defines JSON as a serialized value. Objects contain double-quoted string names, a colon between each name and value, and a comma only between members. Arrays likewise use commas only between values. JavaScript object-literal conveniences do not automatically belong to JSON.
| Problem | Invalid | Valid JSON |
|---|---|---|
| Single quotes | {'name': 'Ada'} | {"name": "Ada"} |
| Unquoted name | {name: "Ada"} | {"name": "Ada"} |
| Trailing comma | {"active": true,} | {"active": true} |
| JavaScript value | {"score": undefined} | {"score": null} |
| Uppercase literal | {"active": True} | {"active": true} |
| Unescaped newline | {"note": "line 1 line 2"} | {"note": "line 1\nline 2"} |
Values such as NaN, Infinity, functions, comments, and undefined are not part of JSON grammar. A top-level string, number, boolean, or null is valid under RFC 8259, although some older systems accept only an object or array.
The same parser error can enter through different boundaries
The parser message describes the text, not where that text came from. Locating the boundary that introduced it is often more useful than staring at the final JSON.parse() call.
Fetch and response.json()
response.json() reads the response stream and parses it. If parsing fails, temporarily read the same request with response.text() so you can see whether the server sent JSON, HTML, or an empty body. Also check response.ok; Fetch does not reject its promise merely because the HTTP status is 404 or 500.
Axios and other HTTP clients
Some clients automatically deserialize JSON responses. Inspect the type of response.databefore parsing it again. If it is already an object or array, use it directly. If it is a string, inspect its first characters and the response headers before deciding how to parse it.
Environment variables and optional values
A missing environment variable or object property evaluates to undefined. Passing that value to JSON.parse() commonly creates the u at position 0 error. Validate required configuration at application startup and fail with a message that names the missing key.
localStorage and cached data
Browser storage may contain an older format, a partially written value, or plain text from a previous release. Version stored records, handle a missing key separately, and remove or migrate entries that no longer match the expected JSON shape. Successful parsing still does not prove the object has the fields your current code requires.
What does “position 0” actually point to?
Position 0 is the beginning of the string supplied to the parser. Higher position numbers point near the character where the parser could no longer continue, not necessarily where the original mistake began. A missing quote or bracket earlier in the text can make a later character look unexpected.
Error formats are engine-specific. One browser may report a zero-based position while another reports a line and column. Use the number to create a small context window, then inspect the structure before and after it.
function parseJsonWithContext(raw) {
try {
return { ok: true, value: JSON.parse(raw) };
} catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error),
preview: JSON.stringify(raw.slice(0, 120)),
};
}
}For long payloads, calculate the line and column from the characters before the reported position or paste the captured text into the ONLINTools JSON Formatter. Formatting valid sections makes mismatched brackets and missing separators easier to see.
Prevent parsing failures at the boundary
- Serialize with a JSON library. Generate payloads with
JSON.stringify()or the equivalent serializer in your language instead of concatenating strings by hand. - Return consistent error envelopes. If an API promises JSON, its error responses should also use JSON and the appropriate HTTP status.
- Test non-success paths. Authentication expiry, rate limits, reverse-proxy failures, and missing routes are frequent sources of HTML bodies in JSON clients.
- Validate at system boundaries. Check untrusted API, file, queue, and local-storage data before the rest of the application depends on its shape.
- Log safely. A short preview helps debugging, but redact access tokens, personal data, and secrets before storing request or response bodies.
Common questions about unexpected JSON tokens
Why does the error happen only in production?
Production adds authentication redirects, CDNs, proxies, rate limits, and custom error pages. Compare status, headers, redirect history, and the first part of the production response with the working development response.
Should I use response.json() or JSON.parse()?
Use response.json() when you expect a normal JSON response and want the Fetch API to read and parse it. During diagnosis, response.text() followed by JSON.parse(raw)exposes the exact body that failed.
Can a byte order mark cause position 0?
It can in parsers that reject a leading byte order mark. RFC 8259 says networked JSON must not add a BOM, though parsers may choose to ignore one for interoperability. Fix the file or producer encoding instead of assuming every consumer will tolerate it.
Is the ONLINTools JSON Formatter private?
Formatting and validation run locally in the browser. Even so, remove production credentials and personal information from debugging samples whenever practical.