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.