JSON Toolkit

JSON Error Message Lookup: From Error Text Straight to the Fix

Bottom line: Look up the cause by the exact JSON.parse message: 12 frequent errors across Chrome, Firefox and Safari, including HTML error pages, truncation and double parsing.

Advanced~8 min readPublished 2026-10-30Updated 2026-11-04
JSON error message lookup table cover

Bottom line: when a JSON.parse error appears, grab the position and the token first; a position of 0 means checking for an HTML response, a BOM, or the wrong type, and an "Expected" message means looking at the previous character — do not re-read line by line from the reported position.

The error text is not noise — it is the answer. The parser tells you which character it hit, what it expected, and what it actually got. Searching by the error message beats rewriting the whole document by hand.

Every JSON.parse() error has three parts: the position (a character offset, or line/column), the expectation (Expected...), and the reality (Unexpected token X, or xxx is not valid JSON). Engines word them differently — Chrome and Node (V8) say Expected ',' or '}' after property value in JSON at position 25, Firefox says JSON.parse: expected ',' or '}' after property value in object at line 3 column 1, Safari says JSON Parse error: Expected ',' or '}' — but all three point at the same syntax break. When you look an error up, ignore the wording and grab the three facts: which character, what was expected, what arrived.

1. The 12-error lookup table

The left column is the most identifiable fragment from your console (V8 wording). Firefox/Safari variants are in parentheses.

Error text (V8) Real cause Fix
Unexpected token < in JSON at position 0 The API returned HTML (404 page, 500 template, login redirect, proxy fallback), not JSON Check response.status and Content-Type first; do not fix the JSON
Unexpected end of JSON input Input is empty or truncated: empty body, 204, interrupted download, copy missing the last line Handle the empty case, compare Content-Length to find the cut
Unexpected token o / "[object Object]" is not valid JSON An already-parsed object was passed to JSON.parse() again and coerced to a string Check typeof; if it is an object, use it directly
"undefined" is not valid JSON The variable was never assigned (JSON.parse(undefined)) Initialize fallback values; guard before parsing
Expected double-quoted property name in JSON A trailing comma ({"a": 1,}); the parser expects another key and finds } Remove the comma after the last item
Expected property name or '}' in JSON An unquoted or single-quoted key ({name: "Ada"}, {'name': 'Ada'}) Double-quote every key
Expected ',' or '}' after property value in JSON A missing comma ({"a": 1 "b": 2}) or a comment after a value Add the comma; strip the comment
Unexpected token '/' A // or /* */ comment inside JSON Remove it, or parse the config as JSONC
Bad control character in string literal A raw newline, tab, or other U+0000–U+001F control character inside a string Write \n; generate with JSON.stringify
Unterminated string in JSON A string opened with " and never closed, or an inner quote was not escaped Escape inner quotes as \"
Unexpected non-whitespace character after JSON Two documents in one text: {}{}, NDJSON, a JSONP wrapper Split by lines and parse each, or use an NDJSON parser
JSON.parse is not a function Your own variable shadows the global JSON Rename the variable; never name it JSON

The first two deserve a closer look, because neither means "your JSON is wrong" — they mean "what you received is not what you think it is".

2. Three common sources of a position-0 error

A position of 0 means the very first character is illegal. If nothing looks suspicious, the character is probably invisible. Three usual suspects:

First: <, meaning HTML. This is the most common parse error in production. A wrong URL, an expired session redirected by the gateway, or a server error template — the body starts with <!DOCTYPE html> and the parser quits on character one. The correct move is to open the Network panel and read the raw response, then fix the API call, not the JSON.

Second: a BOM or zero-width characters. Files saved by Windows Notepad often start with a UTF-8 BOM (\uFEFF), and text copied from web pages or chat apps may carry zero-width spaces (\u200B–\u200D). Invisible to you, obvious to the parser:

// Strip BOM and zero-width characters before parsing
const cleaned = raw.replace(/^\uFEFF/, '').replace(/[\u200B-\u200D]/g, '');
const data = JSON.parse(cleaned);

Third: the wrong type. JSON.parse(undefined) reports "undefined" is not valid JSON; JSON.parse(someObject) reports Unexpected token o (the object was coerced to "[object Object]", and o is illegal at position 1). Both mean "the data is already parsed" or "there was never any data". Check the type, not the syntax:

let data = text;
if (typeof data === 'string') {
  data = JSON.parse(data); // Parse only when it really is a string
}

3. The position points downstream; the cause is upstream

Two errors mislead you about location, and they are the two most misread entries in the table:

  • A missing comma: {"a": 1 "b": 2} reports Expected ',' or '}' after property value at the "b" line. The report is correct, but the fix belongs at the end of the previous line.
  • A missing closing bracket: deep nesting missing one } or ] makes the parser swallow everything to the end of input and report Unexpected end of JSON input. The position is the end of the file; the real gap can be far earlier. Search backwards for the most recent unclosed { or [ — counting pairs beats eyeballing.

The general technique is bisection: cut the text before the reported position and parse it alone. If it parses, the problem is near the reported position; if not, bisect further back. The boundary of the first failing chunk is the real error site. For large files, format the document first — with indentation aligned, the missing level is visible at a glance (the online JSON formatter works fine for this).

4. Four "false healthy" states that parse fine but misbehave

Worse than an error is no error and wrong data. In all four cases the JSON is valid and the parser is happy — the problem is the shape of the data:

  1. Double encoding: the whole document was JSON.stringify-ed twice, so parsing yields a string, not an object. If typeof result === 'string', parse again — but find and fix the double serialization at the source.
  2. Duplicate keys silently overwrite: {"name": "Tom", "name": "Jerry"} is valid JSON, most parsers do not complain, and the second value wins. This bites when merging data from several sources; surface it with a strict JSON validator before it bites you.
  3. NDJSON treated as one document: streaming APIs emit one object per line; parsing the whole text fails with Unexpected non-whitespace character after JSON. Split on \n and parse per line.
  4. Silent number precision loss: integers above 2^53 - 1 (snowflake IDs) come back with the last digits zeroed, with no error at all. Transmit IDs as strings.

The common thread: the parser believes everything is fine. So when API data looks wrong, print the first 80 characters of the raw text and its typeof first — confirm the shape before discussing the content.

5. Make errors impossible at write time

The lookup table is the after-the-fact tool; prevention is the before-the-fact tool. Four habits intercept most errors before they happen:

// 1. Two checks before parsing any response
const response = await fetch('/api/users');
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: the API returned no data`);
}
const contentType = response.headers.get('content-type') ?? '';
if (!contentType.includes('application/json')) {
  throw new Error(`Expected JSON, got ${contentType}`);
}
const data = await response.json(); // 2. json() already parses — do not parse again
  • Serialize to generate, never concatenate: JSON.stringify() handles quotes, newlines, and control characters correctly; hand-built strings are the top source of Bad control character and Unterminated string.
  • Always catch: a thrown JSON.parse error is a synchronous crash unless something catches it. For untrusted input — localStorage, external files — the catch block needs a default value or explicit error reporting.
  • Delegate structure to Schema: valid syntax does not mean a valid structure. Missing fields and wrong types belong to JSON Schema validation at the boundary; see what JSON Schema solves.

6. The quick decision path

Compress the whole article into one decision order. When you see an error, grab the position and the token first:

  1. Position 0 → check the three sources: < (HTML), BOM, wrong type;
  2. end of input → check for an empty or truncated body, then search backwards for unclosed brackets;
  3. Expected ... → look at the previous character; it is usually a missing comma or quote;
  4. after JSON data → multiple documents were concatenated; split by line;
  5. Parses fine but data looks wrong → run the four "false healthy" checks.

Combined with the step-by-step triage in how to fix JSON parse errors, 90% of parse problems resolve within five minutes. Keep the error text itself bookmarked — it is the debugging log the parser wrote for you, its wording changes once every three years, and one lookup pays for a year.

What does "Unexpected token in JSON" mean?

The parser hit a character that is illegal at that spot in the JSON grammar. The token is the rejected character: < usually means the API returned HTML, ' means single quotes, / means a comment, and a plain letter usually means an unquoted key or a JavaScript-only literal such as undefined or NaN. Read the token first, then the position, and the cause narrows to one.

How do I fix "Unexpected end of JSON input"?

Two sources: empty input (an empty response body, or an empty string in localStorage) or truncated input (an interrupted download, a copy missing the last line, a size limit). Handle the empty case with if (!text) return; then verify completeness and close the most recent unclosed {, [, or ". Note the error can also come from a deep missing bracket — the position just happens to be the end of the file.

Why do Chrome, Firefox, and Safari report errors differently?

The three browsers ship different JS engines (V8, SpiderMonkey, JavaScriptCore), and each words its errors its own way; MDN documents all official variants. But all three errors carry the same three elements — position, expectation, reality — with one-to-one semantics. Translate to the V8 wording and use this table as-is.

The error position points somewhere that looks fine — now what?

The real error is earlier. Typical cases: a missing comma (reported on the next line) and a missing closing bracket (reported at end of file). Walk backwards from the reported position token by token, or bisect: parse the first half alone, and the boundary of the first failing chunk is the true error site.