DevRivet

Blog ·

Unexpected Token in JSON at Position: Causes and Fixes

Unexpected token in JSON at position means JSON.parse hit a character it does not accept. Causes: HTML pages, trailing commas, single quotes, BOMs.

JSON Formatter / Validator

Pretty-print, minify, and validate JSON instantly.

Try it free →

"Unexpected token in JSON at position N" means JSON.parse() found a character at index N that cannot appear at that point in valid JSON. The fix depends on what that character is, and the most common culprit is not your JSON at all but an HTML error page that your code tried to parse.

If you have a string and want to see exactly where it breaks, paste it into the JSON Formatter / Validator. It reports the error line and column, which is easier to read than a character index. DevRivet's JSON Formatter runs entirely in your browser, so you can paste a response that contains tokens or customer data without it being sent to a server.

What the position number means

The number is a zero-based index into the string you passed to JSON.parse(), counted in UTF-16 code units. It is not a byte offset into the file on disk. If the file has multibyte characters before the error, the number will not match what a hex editor shows.

The wording varies by runtime. Older V8 builds (Chrome, Node) print the message shown above. Newer builds phrase it differently, for example "<!DOCTYPE "... is not valid JSON. Firefox and Safari use their own messages. The underlying problem is the same.

Start by logging the raw text before parsing it. Many bugs come from parsing something that is not the JSON you think it is.

try {
  JSON.parse(text);
} catch (err) {
  console.error(err.message);
  console.error("length:", text.length);
  console.error("first 100 chars:", text.slice(0, 100));
}

Cause 1: The server returned an HTML page

This is the most common cause. If the body is an HTML page, the first character is usually <, so the error points at position 0. Typical sources are a 404 or 500 page, a login redirect that returns the sign-in page, a reverse proxy or CDN error page, or a framework error handler that renders HTML.

Note that fetch() does not reject on a 404 or 500. It only rejects on network failures, so you have to check res.ok yourself. Check the status and the Content-Type before parsing:

async function fetchJson(url) {
  const res = await fetch(url);
  const body = await res.text();

  if (!res.ok) {
    throw new Error(`HTTP ${res.status} from ${url}: ${body.slice(0, 200)}`);
  }

  const type = res.headers.get("content-type") ?? "";
  if (!type.includes("json")) {
    throw new Error(`Expected JSON but got "${type}": ${body.slice(0, 200)}`);
  }

  return JSON.parse(body);
}

The check uses includes("json") rather than an exact match because some APIs use other JSON media types, such as application/problem+json.

Cause 2: Trailing commas

JSON does not allow a comma after the last member of an object or the last element of an array. JavaScript and many config formats allow it, so the mistake is easy to make.

{
  "name": "api",
  "tags": ["json", "http"],
}

The error points at the closing brace or the character right after the comma. Remove the comma and it parses.

This bites people with tsconfig.json. TypeScript reads that file with its own parser, which accepts comments and trailing commas. The file looks fine in the editor, then fails when a script passes it to JSON.parse().

Cause 3: Single quotes and unquoted keys

JSON strings and keys must use double quotes. A JavaScript object literal such as {'name': 'api'} fails at the first single quote, which is usually position 1. Unquoted keys, such as {name: "api"}, fail the same way.

Python is a frequent source of this. print(d) on a dict shows single quotes and Python's True, False and None, none of which are valid JSON. Use json.dumps() instead:

import json

print(json.dumps({"name": "api", "active": True}))
# {"name": "api", "active": true}

Cause 4: A byte order mark (BOM)

Some editors, especially on Windows, save UTF-8 files with a byte order mark: three bytes (EF BB BF) that decode to the character U+FEFF at the start of the file. The mark is invisible in most editors. JSON.parse() does not skip it, so the error points at position 0 and the first character looks like a valid {.

If the text is read as UTF-8 and you suspect a BOM, check the first code unit and strip it:

const clean = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
const data = JSON.parse(clean);

In Python, open the file with the utf-8-sig codec, which removes the BOM if present:

import json

with open("config.json", encoding="utf-8-sig") as f:
    data = json.load(f)

Cause 5: Empty or truncated responses

Parsing an empty string does not produce a position. In V8 it throws "Unexpected end of JSON input". A truncated body, such as one cut off by a dropped connection or a size limit, usually gives the same message or an error near the end of the text.

A common case is calling res.json() on a 204 No Content response, or on a DELETE endpoint that returns nothing. Handle empty bodies explicitly:

const body = await res.text();
const data = body.trim() === "" ? null : JSON.parse(body);

Cause 6: Parsing a value that is not a string

JSON.parse() converts its argument to a string first. If you pass an object, you get "[object Object]", and the error is Unexpected token o in JSON at position 1, because the second character is o. Passing undefined gives Unexpected token u at position 0, since the string is "undefined".

If a value is already an object, use it directly. Only parse strings that actually contain JSON.

Other things that trigger the same error

  • Comments. Lines starting with // and block comments with /* */ are not allowed.
  • Non-JSON values. NaN, Infinity and undefined are not valid JSON values.
  • Leading zeros in numbers, such as 007. Write 7.
  • Raw control characters inside strings, such as a literal newline or tab. They must be escaped as \n or \t.
  • Invalid escape sequences. A Windows path like "C:\users\app" fails because \u must be followed by four hex digits. Write "C:\\users\\app" instead.

A quick checklist

  1. Log the status code, the Content-Type and the first 100 characters of the raw body.
  2. If the body starts with <, you received HTML. Fix the request or the endpoint.
  3. If the first character is a BOM (U+FEFF), strip it before parsing.
  4. If the body is empty, skip parsing or handle the empty case.
  5. Otherwise, paste the text into the JSON Formatter / Validator and fix the reported line and column.

FAQ

Why does the error say position 0?

Position 0 means the first character is wrong. The usual causes are an HTML response starting with <, a UTF-8 BOM, or a non-JSON value such as undefined.

Can JSON.parse accept comments or trailing commas?

No. For files that people edit by hand, use a parser that supports JSONC or JSON5, or strip comments first with a real parser. A regex can break on strings that contain // or /* sequences.

Is the position a byte offset or a character offset?

In JavaScript it is an index into the string, counted in UTF-16 code units, not a byte offset in the file. Python reports character positions in its own messages, so compare against the same decoded text.

JSON Formatter / Validator

Pretty-print, minify, and validate JSON instantly.

Try it free →