How to Fix “Unexpected Token” and Other JSON.parse Errors

“Unexpected token in JSON” rarely means your JSON has a typo. More often the parser was handed something that is not JSON at all: an HTML error page, an empty body, the value `undefined`, or a response cut off halfway. Find out what the parser actually received first. The fix follows from that, and it is usually in the request or on the server, not in the parsing code.

What a JSON.parse error actually means

JSON.parse() turns a JSON string into a JavaScript value and throws a SyntaxError the moment the text stops following JSON grammar. The wording depends on the browser and its version. Current Chrome and Node.js print messages such as Unexpected token '<', "<!DOCTYPE "... is not valid JSON or Expected ',' or '}' after property value in JSON at position 6 (line 1 column 7); older versions said Unexpected token < in JSON at position 0; Firefox reports JSON.parse: unexpected character at line 1 column 1 of the JSON data. All of them describe the same event: the point where the input could no longer be valid JSON.

Treat the named token as the point where parsing became impossible, not always as the original mistake. A missing quote near the beginning can make a comma much later look unexpected. Likewise, “Unexpected end of JSON input” says the parser reached the end while it still expected a quote, bracket, brace or value. Inspect a small area before and after the reported position.

First inspect the raw response, not the parsed object

Capture the input as text before you parse it. In a fetch call, temporarily use response.text() and log the status, the Content-Type header and the first 200 characters of the body. Do not call both text() and json() on the same response: the body is a stream that can be read once, and the second call throws a TypeError because the body has already been used. Call response.clone() first if you really need to read it twice.

Then look at the first character. < is HTML: a login page, a framework error page, a proxy's 502 page or a single-page app's index.html. N is often a plain-text Not Found. A u at position 0 means the argument was undefined, which becomes the string "undefined"; newer engines say "undefined" is not valid JSON. An empty string means there was nothing to parse. None of these is fixed by touching the parsing code, because the bug is in routing, authentication or what the server writes out.

Use the position, line and column efficiently

The position is a zero-based character offset into the string you passed in. On a minified payload, print a window around it, text.slice(117, 157) for position 137, instead of scrolling one very long line. Current V8 also appends the line and column, which helps with pretty-printed files. A formatter helps only once the text is valid, because it has to parse the data before it can re-indent it.

If the error points to the end, move backward and check the final complete property or array item. Truncated network output, an incomplete file write and string concatenation bugs commonly remove a closing delimiter. Count braces only as a rough clue; braces inside strings do not define structure. A real JSON validator understands strings and escape sequences and therefore gives a more reliable location.

Fix trailing commas, quotes and property names

JSON is stricter than a JavaScript object literal. Property names and string values need double quotes, and single quotes, unquoted keys and a comma after the last element are all errors. {"name":"Ada",} fails on the final comma, and {name:"Ada"} fails on the unquoted key.

Do not repair API output with a regex that swaps every ' for " or deletes every comma before }; it rewrites the same characters inside string values too. Fix the producer instead: build a native object and hand it to the platform's JSON encoder rather than joining strings.

Check strings, escapes, numbers and unsupported values

Inside a JSON string, quotation marks and backslashes must be escaped, and a literal newline or tab is not allowed; write \n or \t. The only valid escapes are \", \\, \/, \b, \f, \n, \r, \t and \u followed by four hex digits. A Windows path such as C:\temp\x.json typed into a string shows the trap: \t is a valid escape and silently becomes a tab, while \x fails with Bad escaped character in JSON at position ….

JSON numbers cannot have a leading +, leading zeros such as 01, a trailing decimal point or the values NaN and Infinity. There is also no undefined, no comments, no functions and no date type. Send null or omit the property for missing data, and agree on a string format for dates. The literals are lowercase: true, false and null.

Why APIs return HTML, empty text or truncated JSON

When the first character is <, check the request URL, the status and any redirects. A single-page app's server may answer an unknown /api/... route with index.html and status 200. An expired session may redirect to an HTML sign-in form. A reverse proxy can return its own gateway page before your application runs. A request to the wrong host, or one without an Accept: application/json header, can get an HTML representation.

Empty bodies are legitimate for 204 No Content, and they also come with 304 Not Modified and HEAD requests. JSON.parse('') throws Unexpected end of JSON input, so check for an empty body before parsing. A body that ends halfway through a string or array points to a server exception during streaming, a dropped connection or a proxy size limit; the server and proxy logs will tell you which.

A safer fetch pattern for JSON APIs

Read the body once as text, handle the empty case explicitly, and only then parse inside try/catch. When parsing fails, record the URL, status, content type and a short preview, without tokens or personal data, so the next failure explains itself instead of leaving you with a bare SyntaxError.

async function getJson(url, options) {
  const res = await fetch(url, options);
  const text = await res.text();
  if (!res.ok) throw new Error(`${res.status} from ${url}: ${text.slice(0, 200)}`);
  if (text === '') return null;                        // 204 and other empty bodies
  try {
    return JSON.parse(text);
  } catch {
    const type = res.headers.get('content-type');
    throw new Error(`Not JSON (${type}) from ${url}: ${text.slice(0, 200)}`);
  }
}

Content-Type is a hint, not proof: a server can label an HTML page application/json, or send valid JSON as text/plain. That is why a client should decide by trying to parse the text and reports the content type only as a clue. Where an endpoint legitimately returns no content, return null or a domain-specific result; for every other success response, require valid JSON so a broken backend shows up in monitoring quickly.

Validate the contract and prevent the error from returning

Valid syntax only proves the text is JSON. It says nothing about whether user.id exists, price is a number or status has an allowed value. Validate important responses after parsing, with a schema library or explicit checks, so a broken contract fails at the network boundary with a clear message instead of as Cannot read properties of undefined three components later.

Add tests for successful JSON, structured error JSON, authentication redirects, 204 responses and malformed or truncated bodies. On the server, use one error format across controllers and middleware, set the correct status and Content-Type, and make sure an uncaught exception on an API route is rendered as JSON, not as the framework's HTML error page.

Quick troubleshooting checklist

Confirm the exact request URL and method; inspect status, redirects and Content-Type; read the raw body once; check its first character and its ending; use the reported position as a search window; then validate the payload. If it is HTML or empty, debug the endpoint. If it is almost JSON, fix the producer, serializer or source record.

Before closing the issue, reproduce the failure paths as well: an expired session, a mistyped route and an empty result should each produce a response the client handles on purpose, not a SyntaxError in the console.

Frequently asked questions

What causes “Unexpected token < in JSON at position 0”?

The response usually starts with HTML rather than JSON. Check the URL, status, redirects, authentication and raw response body; common sources are 404 pages, login screens and proxy errors.

How do I fix “Unexpected end of JSON input”?

Check for an empty or truncated body and for missing closing quotes, brackets or braces. Do not parse a legitimate 204 response, and inspect server logs if output stops partway through.

Can JSON use single quotes or trailing commas?

No. JSON strings and property names use double quotes, and the final member of an object or array cannot have a trailing comma.

Should I wrap every JSON.parse call in try/catch?

Catch parsing at untrusted boundaries so you can return a useful error, but also validate status and content type. A catch block should not silently replace broken data with an empty object.

Is valid JSON automatically a valid API response?

No. Syntax validation says only that parsing is possible. Validate the resulting fields, types and allowed values against the API contract.