JSON Parse Error: Unexpected Token — What Every Variant Means

Quick answer

💡The unexpected token error from JSON.parse tells you what character the parser encountered and where. The character in the message is the first letter of the invalid content: u means the string 'undefined' was passed, < means an HTML page was returned, o means a JavaScript object was passed without stringifying, and ' means single quotes were used instead of double quotes. Fix by validating input type and content before parsing.

Error symptoms

  • SyntaxError: Unexpected token u in JSON at position 0
  • SyntaxError: Unexpected token < in JSON at position 0
  • SyntaxError: Unexpected token o in JSON at position 1
  • SyntaxError: Unexpected token ' in JSON at position 0
  • SyntaxError: Unexpected token , in JSON at position N
  • SyntaxError: Unexpected token / in JSON at position N

Common causes

  • Passing the JavaScript value undefined to JSON.parse, which coerces to the string 'undefined'
  • A fetch request receiving an HTML error page instead of a JSON response from the server
  • Passing a plain JavaScript object to JSON.parse instead of first calling JSON.stringify on it
  • JSON string built with single quotes instead of double quotes around keys or values
  • Trailing comma left after the last property of an object or last element of an array
  • JavaScript comment syntax included in what was intended to be JSON

When it happens

  • When an API endpoint returns a 404 or 500 HTML error page and the client calls response.json()
  • When localStorage.getItem returns null for a missing key and that null is passed to JSON.parse
  • When a developer accidentally passes a JavaScript object instead of a serialized string to JSON.parse
  • When a JSON file is authored manually with JavaScript-like syntax including comments and trailing commas
  • When a server-side template engine injects an unescaped variable into a JSON response body

Examples and fixes

When a server returns an error page with status 404 or 500, the response body is HTML not JSON. Calling response.json() directly will throw 'Unexpected token < in JSON at position 0' because HTML starts with a less-than sign.

Guarding fetch before calling .json() to prevent HTML parse errors

❌ Wrong

async function fetchProductData(productId) {
  const response = await fetch(`/api/products/${productId}`);
  // If server returns 404 HTML page, this throws:
  // SyntaxError: Unexpected token < in JSON at position 0
  const productData = await response.json();
  return productData;
}

// A 404 response has body: <!DOCTYPE html><html>...
// JSON.parse sees '<' as first char and throws immediately
fetchProductData(9999).catch(err => console.error(err.message));

✅ Fixed

async function fetchProductData(productId) {
  const response = await fetch(`/api/products/${productId}`);

  if (!response.ok) {
    const errorBody = await response.text();
    throw new Error(
      `API error ${response.status} for product ${productId}. ` +
      `Body preview: ${errorBody.substring(0, 100)}`
    );
  }

  const contentType = response.headers.get('content-type') || '';
  if (!contentType.includes('application/json')) {
    const body = await response.text();
    throw new Error(
      `Expected JSON but got ${contentType}. Body: ${body.substring(0, 100)}`
    );
  }

  return response.json();
}

fetchProductData(9999)
  .then(data => console.log(data.name))
  .catch(err => console.error(err.message));

When a server returns a non-2xx status, the body is typically an HTML error page generated by the web server or framework rather than the API handler. HTML documents start with a less-than sign from the DOCTYPE or html tag, which JSON.parse sees as the first character and immediately rejects. The fix checks response.ok before attempting to parse, reads the body as text for error reporting, and also validates the Content-Type header. These two checks together cover the most common cases where the body is not JSON despite a successful HTTP connection.

When localStorage returns null for a missing key, or when an optional value is undefined, passing that value to JSON.parse coerces it to the string 'undefined', which starts with the letter u — causing the error.

Fixing unexpected token 'u' from undefined being passed to JSON.parse

❌ Wrong

// User visits the site for the first time - no saved preferences
const savedPreferences = localStorage.getItem('userPreferences');
console.log(savedPreferences); // null

// This throws: SyntaxError: Unexpected token u in JSON at position 0
// because JSON.parse(null) coerces null to the string 'null'
// which IS valid JSON... but this pattern also fails:
const rawValue = undefined;
const parsedValue = JSON.parse(rawValue);
// 'undefined'.toString() === 'undefined'
// JSON.parse('undefined') throws immediately

console.log(parsedValue.theme); // never reached

✅ Fixed

const DEFAULT_PREFERENCES = {
  theme: 'light',
  language: 'en',
  notificationsEnabled: true
};

function loadUserPreferences() {
  const raw = localStorage.getItem('userPreferences');

  if (raw === null || raw === undefined || raw.trim() === '') {
    return DEFAULT_PREFERENCES;
  }

  try {
    const parsed = JSON.parse(raw);
    if (typeof parsed !== 'object' || parsed === null) {
      return DEFAULT_PREFERENCES;
    }
    return { ...DEFAULT_PREFERENCES, ...parsed };
  } catch (err) {
    console.warn('Failed to parse stored preferences, using defaults:', err.message);
    return DEFAULT_PREFERENCES;
  }
}

const userPreferences = loadUserPreferences();
console.log(userPreferences.theme); // 'light'

JSON.parse coerces its argument to a string before parsing. When undefined is passed, it becomes the string 'undefined', which the parser encounters as the letter u — not a valid start of any JSON value. The fix checks for null and undefined before parsing, since localStorage.getItem returns null for missing keys. Wrapping the parse in try-catch handles any other malformed stored value gracefully by falling back to defaults rather than crashing. The final spread merges stored values onto defaults so that new default keys added in later app versions are still available.

Token-by-token: what each unexpected-token means

The character reported in an unexpected token error is the first letter of the invalid content that the JSON parser encountered. Understanding the mapping between that character and its likely cause is the fastest path to a diagnosis.

Unexpected token u at position 0 nearly always means the string 'undefined' was passed to JSON.parse. This happens when a JavaScript variable holding the value undefined is passed directly, or when localStorage.getItem returns null for a missing key and the null is compared incorrectly, or when an API response variable is undefined because the async assignment was not awaited properly. The u is the first character of the word 'undefined' after type coercion to string.

Unexpected token < at position 0 means the input starts with a less-than sign — the characteristic start of an HTML document. The parser is receiving the text of an HTML error page rather than JSON. This is the classic symptom of a server returning a 404 or 500 error page while the client attempts to parse the body as JSON. The full body typically begins with <!DOCTYPE html> or just <html>.

Unexpected token o at position 1 means the input is the string '[object Object]', which is what JavaScript produces when a plain object is coerced to a string. This happens when a JavaScript object is passed directly to JSON.parse without first calling JSON.stringify on it. The string representation of an array coerced the same way produces something parseable, which can obscure this bug for array inputs.

Unexpected token ' at position 0 means the JSON string uses single quotes rather than double quotes for string delimiters. Single quotes are valid in JavaScript object literals but are not permitted by the JSON specification. JSON.parse sees the single quote as the start of a value and does not recognize it as a string delimiter.

Unexpected token , at a non-zero position means there is a trailing comma somewhere in the JSON. The position helps locate it: navigate to that offset in the string to find the comma before a closing bracket or brace. Trailing commas are permitted in ES2017 JavaScript but have never been part of the JSON specification.

Unexpected token / at any position means there is a JavaScript-style comment in the JSON. Both double-slash line comments and slash-star block comments are JavaScript syntax, not JSON syntax. The parser sees the opening slash and cannot match it to any valid JSON token.

Reading position offsets to locate the bad token

Diagnosing unexpected token failures begins with matching the token character to its likely source. Each character tells a specific story: an apostrophe or single quote points to Python dict output or hand-written strings; a forward slash signals a comment (// or /*) copied in from JavaScript; the letter u at position 0 means the string "undefined" was coerced to a string and passed to JSON.parse; an angle bracket at position 0 means the server returned an HTML error page instead of JSON. Knowing which token appeared is half the diagnosis. For small JSON strings you can count characters manually, but for any realistic payload you need a programmatic approach.

The most direct method is to slice the string around the reported position. rawInput.slice(Math.max(0, position - 20), position + 20) gives you a 40-character window centered on the error. This window almost always contains enough context to identify the problem visually without reading the entire input. Log this window alongside the error message in your catch block.

For multi-line formatted JSON files, the position needs to be converted to a line and column number. Count newlines in rawInput.substring(0, position) to get the line, then find the last newline before position and subtract its index from position to get the column. Many developers find it easier to run multi-line JSON files through an external tool: jsonlint and jq both output errors in line-and-column format rather than position offsets, which maps directly to what a text editor shows.

When the error is at position 0, the problem is in the very first character of the input. Log rawInput.charCodeAt(0) to see the numeric code of that first character. Code 60 is a less-than sign, indicating HTML. Code 39 is a single quote. Code 117 is the letter u. Code 65279 is the UTF-8 BOM. Code 0 or a very small number may indicate a binary file was accidentally read as text. Each of these codes points to a specific cause.

When the error says 'in JSON at position 1' — note position 1, not 0 — the likely cause is the string '[object Object]'. The opening bracket at position 0 is a valid JSON array start, but the o at position 1 is not a valid continuation inside a JSON array, producing the error at position 1.

Guarding fetch responses before calling .json()

The fetch API's response.json() method is a common source of unexpected token errors because it calls JSON.parse on the response body without any guards. When the server returns an HTML error page, the body starts with a less-than sign and JSON.parse immediately throws. The response.json() promise rejects with a SyntaxError, but the error message does not tell you what the actual response body was.

The recommended pattern is to always call response.text() first when you need the raw body for logging or validation, then call JSON.parse manually inside a try-catch. This gives you access to the raw string both before parsing, to log it if the parse fails, and after a successful parse, to work with the data. The extra text() call is not meaningfully slower than json() because the body parsing happens regardless.

Additionally, check response.ok before attempting to parse. response.ok is true for status codes 200 through 299. For any other status code, read the body as text and throw an error with the status code and body preview. This prevents you from attempting to parse HTML error pages at all.

Checking the Content-Type response header before parsing provides a second layer of defense. An API that promises to return JSON should have a Content-Type of application/json. If the header is text/html, the body is definitely not JSON and you can skip the parse attempt entirely and report the content type as part of the error. Many CDNs and reverse proxies return HTML error pages with a text/html Content-Type even when proxying API endpoints, making this check useful in production.

For applications that use a shared HTTP client layer, implement these guards once in the client library rather than repeating them at every call site. A fetchJson utility function that performs the ok check, the content-type check, reads the body as text, and then parses it in a try-catch provides a consistent, debuggable interface for all JSON API calls in the codebase.

Node versus browser unexpected-token messages

The exact format of the unexpected token error message differs between JavaScript environments, which matters when you are processing error messages programmatically or matching them in monitoring tools.

Node.js with V8 produces: SyntaxError: Unexpected token X in JSON at position N. Older Node.js versions (before v12) use slightly different wording that may omit 'in JSON'. Node.js v20 and later may produce messages in the format: SyntaxError: Expected property name or '}' in JSON at position N, which is a more descriptive format introduced in newer V8 versions. If your error monitoring tool alerts on JSON SyntaxError messages, test that your alert rule matches all variants.

Firefox with SpiderMonkey produces more verbose messages: SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data. The message includes 'JSON.parse:' as a prefix, followed by a description of what was expected versus what was found, and gives line and column rather than a position offset. This format is more human-readable but harder to parse programmatically if you are trying to extract the position.

Safari with JavaScriptCore produces: SyntaxError: JSON Parse error: Token 'X' is not valid JSON. The Safari message uses the word 'Token' and quotes the invalid character. It does not include a position number, which makes it less useful for locating the error in a long string.

In Deno, the error format matches V8 since Deno uses V8, but the stack trace format differs. In Bun, which uses JavaScriptCore, the error messages match Safari's format. If your application runs in multiple environments, write error-processing code that handles all three formats: V8 with position, SpiderMonkey with line/column, and JavaScriptCore without position.

When HTML error pages masquerade as JSON

The most operationally painful category of unexpected token errors is when a properly functioning API endpoint suddenly starts returning HTML error pages due to an infrastructure change. This can happen when a CDN misconfiguration starts serving a cached error page, when a load balancer health check fails and starts routing to an error handler, when a reverse proxy applies a rate limit and returns its own error page, or when a deployment leaves an application in an error state that makes the framework's default error handler return HTML.

These incidents share a characteristic pattern: the API was working, then it starts producing unexpected token errors for many or all callers simultaneously, often corresponding to a deployment or infrastructure change. The error message 'Unexpected token < in JSON at position 0' is the signature. If you see this error appearing across multiple unrelated operations at the same time, the problem is almost certainly not in your parsing code — it is in the server returning HTML instead of JSON.

Diagnosing this requires inspecting the actual response body, which means your error handling must preserve it. Without the raw body, you know something is wrong but not what the server sent. With the raw body, you can immediately determine whether it is an HTML error page, a proxy error message, a rate limit response, or something else.

The Content-Type header is not always reliable for catching this. Some error pages are served with a Content-Type of application/json even though the body is HTML or plain text. This happens with certain API gateways and older framework error handlers that set the wrong Content-Type on error responses. The safest approach is to check both the Content-Type and the first character of the body — if the body starts with '<' or '!', it is HTML regardless of what the Content-Type claims.

For production alerting, creating a specific alert for JSON parse errors with 'Unexpected token <' in the message helps distinguish these infrastructure-level failures from ordinary single-instance parse errors. A spike in this specific error is a reliable signal that an upstream service is returning HTML error pages.

Defensive JSON parsing in production APIs

Production APIs that accept JSON request bodies and return JSON responses should apply defensive practices at every boundary where JSON is parsed, not just at the main data entry points.

For incoming request bodies, validate the Content-Type request header before parsing. If a client sends a request with Content-Type: text/plain but a JSON-shaped body, a strict parser will still parse it. Being explicit about the expected Content-Type prevents situations where malformed JSON from an unexpected source gets into your processing pipeline. Return a 415 Unsupported Media Type response with a clear message when the Content-Type is wrong.

For all JSON.parse calls in server code, wrap them in a utility that adds the source context to any error it throws. Include the name of the subsystem (config loader, queue consumer, external API client) and a sanitized preview of the raw input. This context is what makes production errors actionable. Without it, a JSON parse error in the logs requires tracing through code to determine which of possibly dozens of JSON.parse calls is the source.

For response bodies sent to clients, serialize with JSON.stringify inside a try-catch to protect against circular references or other serialization errors. A circular reference in a response object causes stringify to throw, which if uncaught produces an internal server error. Catching it explicitly lets you return a structured error response rather than crashing the request handler.

For JSON stored in databases — a common pattern with JSONB columns in PostgreSQL or JSON columns in MySQL — validate the structure of the JSON before using it rather than assuming the stored value is always valid. Database constraints may enforce valid JSON syntax but do not enforce your application's expected schema. A schema validation step using a library like ajv after parsing caught-in-database JSON prevents downstream errors when the schema has changed or data was written by a different application version.

Quick fix checklist

  • Check response.ok before calling response.json() in any fetch call
  • Validate the Content-Type header is application/json before parsing
  • Guard against undefined and null before passing values to JSON.parse
  • Use rawInput.charCodeAt(0) to identify the first character when error is at position 0
  • Log the raw body preview whenever a JSON parse error is caught
  • Search for single quotes, trailing commas, and comment syntax in hand-authored JSON
  • Call JSON.stringify on JavaScript objects before passing them to JSON.parse
  • Set up specific alerting for 'Unexpected token <' to catch HTML-instead-of-JSON incidents

Related guides

Frequently asked questions

What does 'Unexpected token u in JSON at position 0' mean?

The letter u at position 0 means the string 'undefined' was passed to JSON.parse. JavaScript coerces undefined to the string 'undefined' before parsing, and the parser sees the letter u as the first character, which is not a valid start for any JSON value. The fix is to check that the value is a non-empty string before passing it to JSON.parse, and return a default value when the input is absent.

What causes 'Unexpected token < in JSON at position 0'?

The less-than sign at position 0 means the input is an HTML document, not JSON. This almost always happens when a server returns an HTML error page — a 404 Not Found or 500 Internal Server Error page — and the client code calls JSON.parse on the body without first checking the response status or Content-Type header. Always check response.ok and the Content-Type before calling response.json() or JSON.parse.

Why does 'Unexpected token o in JSON at position 1' occur?

Position 1 with the letter o means the input is the string '[object Object]', which is what JavaScript produces when a plain object is coerced to a string. This happens when a JavaScript object is passed directly to JSON.parse without first being serialized with JSON.stringify. The opening bracket at position 0 looks like a valid array start, then the letter o at position 1 fails because it is not a valid array element.

How do I fix 'Unexpected token , in JSON'?

A comma at a non-zero position indicates a trailing comma — a comma after the last element of an array or the last property of an object. JSON strictly disallows trailing commas even though JavaScript object literals and ES2017 array literals permit them. Use the reported position to locate the trailing comma and remove it. A JSON linter or formatter will highlight the exact location.

Why does response.json() throw an unexpected token error?

response.json() calls JSON.parse on the response body text. If the server returns anything other than valid JSON — an HTML error page, a plain text message, or an empty body — JSON.parse throws. To diagnose, call response.text() instead and log the result. Check the response status code: a non-2xx status usually means the body is an error page rather than data.

Can I configure JSON.parse to allow trailing commas or comments?

No, the standard JSON.parse does not support trailing commas, comments, or any other extensions to the JSON grammar. If you need to parse JSON with these features — often called JSON5 — use the json5 npm package, which provides a JSON5.parse method that accepts these extensions. Alternatively, strip comments and trailing commas from the string before passing it to the standard JSON.parse.

How do I detect if a fetch response is HTML before parsing as JSON?

Check two things: the response status code and the Content-Type header. If the status is not in the 200 range, read the body as text for logging and throw an error. If the Content-Type does not include application/json, the body is likely not JSON. As an additional guard, read the body as text first and check whether it starts with a less-than sign before attempting JSON.parse.

What is the difference between 'Unexpected token' errors in Chrome versus Firefox?

Chrome (V8) reports: SyntaxError: Unexpected token X in JSON at position N. Firefox (SpiderMonkey) reports: SyntaxError: JSON.parse: unexpected character at line L column C of the JSON data. Firefox includes 'JSON.parse:' as a prefix and gives line and column numbers rather than a position offset. Safari (JavaScriptCore) reports: SyntaxError: JSON Parse error: Token 'X' is not valid JSON, without a position number.

Why does localStorage.getItem cause a JSON parse error?

localStorage.getItem returns null when the key does not exist, not undefined. JSON.parse(null) coerces null to the string 'null', which is actually valid JSON and parses to the JavaScript value null — but then accessing properties on null throws a TypeError. The safer pattern is to check for null before parsing and fall back to a default value, then also wrap the parse in try-catch to handle cases where a stored value has become corrupted.

All tools run in your browser. Your data never leaves your device. Last updated: 2026-05-05.