Last week, a teammate pinged me at 2 AM: "The API keeps returning SyntaxError: Unexpected token but I can't spot anything wrong." I opened his JSON file, stared at it for ten minutes, and found nothing obvious either. That's when I learned the first lesson about this error — it's never where you think it is.
Let me walk through the actual cases I've encountered, with exact reproduction steps and fixes.
Case 1: The Invisible BOM Character
The JSON looked perfectly valid in every editor. But JSON.parse() kept failing:
const data = `{"name":"Alice","age":30}`;
JSON.parse(data);
// SyntaxError: Unexpected token in JSON at position 0
Position 0? That's the very start. How could it fail at {? I dumped the raw bytes:
console.log(data.charCodeAt(0)); // 65279
65279 — that's the Unicode BOM (Byte Order Mark), aka \uFEFF. My file editor had saved the JSON with UTF-8 BOM, and the parser choked on the invisible prefix.
Fix: Strip the BOM before parsing.
function removeBOM(str) {
if (str.charCodeAt(0) === 0xFEFF) {
return str.slice(1);
}
return str;
}
const cleaned = removeBOM(rawData);
JSON.parse(cleaned); // works
If you're dealing with external files, you can also upload them to the JSON Formatter — it strips BOM automatically and pinpoints the error line.
Case 2: Truncated JSON from a Proxy
Another classic: a service worker or proxy cut the response mid-stream.
{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"},{"id":3,"name":"Char
No closing brackets, nothing after "Char". The browser fetched 200 bytes, but the server sent 400. The connection dropped.
How I caught it: Logged the response length and checked the last 20 characters:
fetch('/api/users')
.then(res => res.text())
.then(text => {
console.log('Received length:', text.length);
console.log('Last 20 chars:', text.slice(-20));
// Received length: 83
// Last 20 chars: ,"name":"Char
// No ending bracket — truncated!
});
Fix: This is a server or proxy issue. Add timeout handling and validate JSON length client-side. If you're debugging a one-off file, paste it into the JSON Formatter — it'll show the incomplete structure in its tree view, making truncation obvious.
Case 3: Hidden Control Characters in Scraped Data
I was scraping product data from an HTML page. After extracting JSON from a <script> tag, JSON.parse() exploded:
SyntaxError: Unexpected token \u0000 in JSON at position 347
A null byte (\x00) had slipped into a product description field during HTML entity decoding.
const raw = await fetchProductJson();
for (let i = 0; i < raw.length; i++) {
const code = raw.charCodeAt(i);
if (code < 32 && code !== 9 && code !== 10 && code !== 13) {
console.log(`Control char at ${i}: \\u${code.toString(16).padStart(4, '0')}`);
}
}
Fix: Strip control characters from incoming data:
function sanitizeJson(str) {
return str.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
}
const safe = sanitizeJson(rawData);
JSON.parse(safe);
Case 4: Single Quotes in a JSON Blob
This one looks silly, but it gets everyone at least once. Someone wrote:
{'name': 'Alice', 'age': 30}
JSON requires double quotes for keys and string values. Single quotes are valid JavaScript object syntax but not valid JSON.
JSON.parse(`{'name': 'Alice'}`);
// SyntaxError: Unexpected token ' (...
Fix: Use double quotes. If you're copying data from a JavaScript file, replace single quotes with double quotes — but watch out for apostrophes in strings.
If you're dealing with a large file full of single-quoted keys, tools like the JSON Formatter can help you validate and fix quote issues with line-level error reporting.
Case 5: Trailing Commas in Array/Object
My teammate's config file:
{
"plugins": [
"parser",
"linter",
"formatter",
]
}
JSON does not allow trailing commas. JavaScript does (since ES2017), but JSON.parse() throws:
SyntaxError: Unexpected token ] in JSON at position 68
Fix: Remove trailing commas. If you're programmatically generating JSON, use JSON.stringify() instead of string concatenation:
const bad = `{"plugins": ["a", "b",]}`;
JSON.parse(bad);
// SyntaxError: Unexpected token ] in JSON...
// Instead, build objects:
const good = JSON.stringify({ plugins: ["a", "b"] });
JSON.parse(good); // works
Reading JSON Errors Efficiently
Most JSON parsers report the error position, but it's the byte offset, not the line number. To convert it:
function findErrorLine(str, pos) {
const lines = str.slice(0, pos).split('\n');
return lines.length;
}
try {
JSON.parse(badData);
} catch (e) {
const line = findErrorLine(badData, +e.message.match(/position (\d+)/)[1]);
console.log(`Error at line ${line}`);
}
The JSON Formatter does this mapping automatically — it shows both the line number and the exact column where parsing failed.
FAQ
Q: Why does JSON.parse() fail at position 0 but my file looks fine?
A: Position 0 failure usually means a BOM character or other invisible bytes at the start of the file. Dump charCodeAt(0) to check.
Q: How do I spot truncated JSON quickly?
A: Check if the string ends with } or ]. If not, the payload was cut off. The tree view in a good formatter makes this immediately visible.
Q: Can trailing commas ever work in JSON?
A: No. JSON specification strictly forbids trailing commas. Only use them in JavaScript object literals.
Q: What tools help with debugging Unexpected Token errors?
A: Any tool with line-level error reporting helps. The JSON Formatter shows the exact line and column, plus a tree view to inspect partial data.
Q: I'm getting "Unexpected token N" — what does that mean?
A: It usually means undefined or NaN was serialized into your JSON. JSON doesn't support these values. Check for JSON.stringify({ key: undefined }) — the key will be dropped silently, potentially breaking downstream parsers.
Q: Can newlines inside string values cause parse errors?
A: Yes! A literal newline inside a JSON string value is invalid. It must be escaped as \n. This often happens when copying multi-line text into a JSON editor.
Q: My JSON works in the browser but fails in Node.js. Why?
A: Check for differences in how the response body was read. Browser fetch automatically decodes, but if you're reading a file manually, encoding mismatches (like UTF-16 vs UTF-8) can inject control characters.
Q: How do I find the line number from the error position?
A: Count newlines before the error position, then add 1. Or use a validator that does this for you automatically.
Give your next stubborn JSON file a quick spin through the JSON Formatter — it catches most of these cases on the first render.