← All posts
Web developmentPublished · 10 min readAlso on dev.to

How to Fix JSON Parse Errors — Unexpected token '<' and More

JSON errors arrive as one terse line, but the causes behind them are a short list: the server sent HTML instead of JSON, there was nothing to parse, or the JSON was written by hand like a JavaScript object. This guide quotes the exact messages Chrome, Node.js and Python print for each case, and what to check for every one of them.

Free tool featured in this guide
JSON Formatter & Validator

Paste JSON to pretty-print it. On a syntax error it shows the message and the line and column, and highlights the line. Everything runs in your browser — nothing is uploaded.

Find the error position

How to read the message

The same broken JSON produces different wording depending on what reads it. Here is what each environment actually printed for {"name": "kim", "age": 20,}, which has a trailing comma.

EnvironmentMessage
Chrome, Edge, Node.jsExpected double-quoted property name in JSON at position 26 (line 1 column 27)
Python jsonExpecting property name enclosed in double quotes: line 1 column 27 (char 26)
Older Chrome / Node.jsUnexpected token } in JSON at position 26

Two rules for the numbers. position (and Python's char) counts characters from 0; line and column count from 1, like your editor. So position 26 and column 27 point at the same character.

After the comma the parser expects another key, meets `}` at position 26 and stops. The thing to fix is the comma right before it.
💡

The reported position is where the parser could no longer continue, not where you made the mistake. Start there and read backwards.

Unexpected token '<' — the server sent HTML, not JSON

This is the error you are most likely to see after calling an API with fetch and then res.json(). The wording differs by browser, the meaning does not.

text
Chrome/Edge : SyntaxError: Failed to execute 'json' on 'Response': Unexpected token '<', "<!DOCTYPE "... is not valid JSON
Firefox     : SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
Safari      : SyntaxError: JSON Parse error: Unrecognized token '<'

The < is the first character of <!DOCTYPE html>. An HTML document arrived where JSON was expected, so staring at the code that builds your JSON will not help. The usual reasons:

  • A typo or a changed API path — the server answers with its 404 page
  • A server-side crash — the 500 error page is HTML
  • An expired session — the request is redirected to a login page
  • A dev server without a proxy — React or Vue dev servers return index.html for /api/...
  • SPA fallback on static hosting — every unknown path serves index.html
Calling `res.json()` without checking the response hides the real cause (a 404 or 500) behind a JSON error.

How to confirm it

  1. 1Open DevTools (F12) → Network → click the request → Response. If you see HTML, you have your answer.
  2. 2Check the Status column for 404, 500 or 302.
  3. 3Look at Content-Type under Headers. text/html means the server never sent JSON.

Guard against it in code

javascript
const res = await fetch("/api/users");
if (!res.ok) {
  throw new Error(`HTTP ${res.status}`);
}
const type = res.headers.get("content-type") ?? "";
if (!type.includes("application/json")) {
  const body = await res.text();
  throw new Error(`Expected JSON, got: ${body.slice(0, 80)}`);
}
const data = await res.json();

Now the console says HTTP 404 instead of Unexpected token '<'. If the status is 200 and you still get HTML, suspect a login redirect or a missing proxy rule.

Unexpected end of JSON input — nothing to parse

text
Chrome/Node : SyntaxError: Unexpected end of JSON input
Python      : json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

The input ended before the parser could read a single value — you parsed an empty string. Note that Python reports an empty string and an HTML page identically, as Expecting value: line 1 column 1 (char 0), so in Python the fastest check is to print what you actually received.

  • The server replied 204 No Content and the code still called res.json()
  • The server sent an empty body on an error (check the status code too)
  • The file is zero bytes, or a program died while writing it
javascript
const res = await fetch("/api/items/3", { method: "DELETE" });
const text = await res.text();
const data = text ? JSON.parse(text) : null; // empty body -> null

Truncated JSON reads differently. If the input stops midway, like {"name": "kim", "tags": ["a", "b", Chrome and Node.js report Expected ',' or ']' after array element in JSON at position 33 — the next required symbol never came. When the position equals the very end of the input, suspect truncation: a log line copied without its tail, or a response cut off by a size limit.

JavaScript syntax is not JSON — the common slip-ups

JSON grew out of JavaScript object notation but is far stricter. Hand-edit a JSON file with object-literal habits and these are the errors you get.

The left side runs fine as JavaScript, yet every highlighted line is invalid JSON. The right side is the same data as valid JSON.
MistakeExampleChrome / Node.jsPython
Trailing comma in object{"a": 1,}Expected double-quoted property nameExpecting property name enclosed in double quotes
Trailing comma in array[1, 2, 3,]Unexpected token ']'Expecting value
Single quotes{'name': 'kim'}Expected property name or '}'Expecting property name enclosed in double quotes
Unquoted key{name: "kim"}Expected property name or '}'Expecting property name enclosed in double quotes
Missing comma{"a": 1 "b": 2}Expected ',' or '}' after property valueExpecting ',' delimiter
Comment{"a": 1 // note}Expected ',' or '}' after property valueExpecting ',' delimiter

Notice that the message rarely names the mistake. A comment does not produce "comments are not allowed"; it produces "expected ',' or '}'". Trust the position more than the wording, and look around it for one of the mistakes in this table.

⚠️

tsconfig.json and VS Code's settings.json are JSONC, which allows comments. Carry that habit into package.json or an API payload and parsing fails.

Values JSON does not have — NaN, True, None, 007

JSON allows exactly six kinds of value: strings, numbers, true/false, null, arrays and objects. Anything borrowed from another language's syntax fails.

ValueChrome / Node.jsFix
NaN, InfinityUnexpected token 'N' / 'I'Store null or a string instead
True, None (Python repr)Unexpected token 'T' / 'N'Build it with json.dumps(), which writes true and null
undefinedUnexpected token 'u'Use null. JSON.stringify drops undefined properties entirely
007 (leading zero)Unexpected number in JSON at position 7The number 7, or the string "007"
0x1F (hex)Expected ',' or '}' after property valueStore the decimal 31

Unexpected token 'N' shows up for both NaN and None. The quoted fragment that follows ("{"score": NaN}" is not valid JSON) is the original text around the problem, so read it to tell which one you have.

When Python-made JSON breaks in JavaScript

Python's json module writes NaN and Infinity by default and happily reads them back. Everything works between Python programs, then a browser or Node.js reads the file and fails.

python
import json

json.dumps({"score": float("nan")})
# '{"score": NaN}'  <- not standard JSON

json.dumps({"score": float("nan")}, allow_nan=False)
# ValueError: Out of range float values are not JSON compliant

With allow_nan=False the error happens at write time, before bad data leaves your program. A related slip: strings made with str(data) or print(data) look like {'ok': True, 'v': None} — single quotes, True, None — and are not JSON. Use json.dumps() whenever you need JSON.

Errors inside strings — newlines, backslashes, BOM

Bad control character in string literal

text
Chrome/Node : Bad control character in string literal in JSON at position 14 (line 1 column 15)
Python      : Invalid control character at: line 1 column 15 (char 14)

A real line break or tab sits inside a quoted string. In JSON a newline inside a string must be written as the two characters \n. Build the JSON with JSON.stringify() or json.dumps() rather than concatenating strings, and this is handled for you.

Bad escaped character — Windows paths

text
Chrome/Node : Bad escaped character in JSON at position 13 (line 1 column 14)
Python      : Invalid \escape: line 1 column 13 (char 12)

In {"path": "C:\Users\kim"} each single backslash starts an escape sequence, and \U is not a valid one. Double the backslashes (C:\\Users\\kim) or use forward slashes (C:/Users/kim) — Python's and Node.js's file functions accept forward slashes on Windows. Also note the one-character difference: Python points at the backslash, Chrome and Node.js at the U after it.

The invisible BOM

text
Chrome/Node : Unexpected token '', "{"a": 1}" is not valid JSON
Python      : Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)

If a file looks perfect but fails on its very first character, suspect a BOM (byte order mark), an invisible marker at the start of the file. In Chrome and Node.js it shows up as Unexpected token '' with seemingly nothing between the quotes. On Windows, PowerShell 5.1 is a frequent source: Set-Content -Encoding UTF8 writes a BOM, and > or Out-File write UTF-16, which Node.js reports as Unexpected token '�'.

  • Python: open the file with encoding="utf-8-sig" and the BOM is skipped.
  • Node.js: JSON.parse(text.replace(/^\uFEFF/, "")) strips a leading BOM.
  • VS Code: click the encoding in the status bar (UTF-8 with BOM), choose Save with Encoding → UTF-8.
  • PowerShell 7 writes UTF-8 without a BOM by default, so it avoids the problem.

"undefined" is not valid JSON — you passed something that is not a string

JSON.parse expects a string. Anything else is converted to a string first, which is why the message quotes that value's string form instead of your JSON.

MessageWhat was passedTypical cause
"undefined" is not valid JSONundefined, or the string "undefined"Reading back a value saved with localStorage.setItem("user", undefined)
"[object Object]" is not valid JSONSomething already an objectParsing axios's response.data or a res.json() result a second time
Unexpected non-whitespace character after JSONTwo JSON values back to backParsing a whole JSON Lines (.jsonl) file in one go
javascript
// Save: store null rather than undefined
localStorage.setItem("user", JSON.stringify(user ?? null));

// Load: getItem returns null if nothing was saved
const raw = localStorage.getItem("user");
const saved = raw ? JSON.parse(raw) : null;

// JSON Lines: parse line by line
const rows = text.split("\n").filter(Boolean).map((line) => JSON.parse(line));

localStorage converts every value to a string, so storing undefined leaves the nine-character string "undefined" behind. If a bad value is already stored, delete that key in DevTools → Application. And axios parses JSON responses for you, so response.data never needs another JSON.parse.

A fast checklist

  1. 1See < in the message? Check the response, not the JSON (Network tab).
  2. 2end of JSON input? Check whether the input is empty or cut off.
  3. 3Got a position or line/column? Go there and look at the character just before it.
  4. 4No position, or a huge file? Paste it into a JSON formatter to see the failing line. On the command line, python -m json.tool file.json prints a location such as line 4 column 1.
  5. 5Generating JSON in code? Stop concatenating strings and use JSON.stringify() or json.dumps(). Nearly every syntax error in this guide comes from hand-built JSON.
ℹ️

The JSON formatter checks your input inside the browser and never sends it to a server, so you can locate errors in data you would not want to paste into an online service.

FAQ

Q. Is there any way to put comments in JSON?

Not in standard JSON. A common workaround is a key such as "_comment": "...". For config files, check whether the program reading them supports JSONC or JSON5. Neither JSON.parse nor Python's json module accepts comments.

Q. The position is one less than my editor's column number.

Position (Python's char) counts from 0 and editor columns count from 1. Current Chrome and Node.js also print (line 1 column 27), which you can feed straight into Go to Line (Ctrl+G in VS Code).

Q. Python reads the file, but JavaScript throws.

It almost certainly contains NaN or Infinity. Python's json module accepts them by default; standard JSON and JavaScript do not. Catch it on the writing side with allow_nan=False, and store None for missing values.

Q. Python writes non-English text as `\ud55c\uae00`. Is it corrupted?

No. json.dumps escapes every non-ASCII character as \u by default, and it decodes back to the original text. If you want the file itself to be readable, use json.dumps(data, ensure_ascii=False).

Q. What changes if I use JSON.parse(await res.text()) instead of res.json()?

The parsed result is the same. Getting the text first means that when parsing fails you can log what actually arrived, which makes the cause far easier to find. In production, logging the first 100 characters or so on failure is a common pattern.

Free tool featured in this guide
JSON Formatter & Validator

Paste JSON to pretty-print it. On a syntax error it shows the message and the line and column, and highlights the line. Everything runs in your browser — nothing is uploaded.

Find the error position

More posts