(TO THE GOD OF ABRAHAM, ISAAC AND JACOB. I DEDICATE THIS WORK TO YOU MAY YOU BLESS IT AND MAY IT BLESS THOSE YOU USE IT, MORESO MAY THEY KNOW YOU BY NAME, REPENT AND BE LED TO YOUR WILL AND KINGDOM.) Our Father who is in the heavens, let Your Name be set-apart,let Your reign come, let Your desire be done on earth as it is in heaven. Give us today our daily bread. And forgive us our debts, as we for- give our debtors. And do not lead us into trial, but deliver us from the wicked one because Yours is the reign and the power and the esteem, forever. Amen.
Why your JSON.parse is failing: 8 hidden traps and how to debug them | devformat.tools Blog
jsondebuggingjavascript

Why your JSON.parse is failing: 8 hidden traps and how to debug them

JSON.parse breaks in surprising ways. A field guide to trailing commas, BOMs, NaN, comment hacks, and 5 more silent killers — with fixes.

By devformat.tools · · 5 min read

Why your JSON.parse is failing: 8 hidden traps and how to debug them

SyntaxError: Unexpected token } in JSON at position 142. You've stared at the payload for ten minutes. It looks fine. Curl prints it fine. jq . parses it fine. But JSON.parse keeps blowing up.

This post is the list I wish I'd had the first time I hit this. Eight traps, in roughly the order you'll meet them, with the actual fix for each.

1. Trailing commas

The most common one, and the most embarrassing.

{
  "name": "alice",
  "role": "admin",
}

RFC 8259 treats commas as separators, not terminators. After a comma, the parser expects another member. It gets } and gives up. JavaScript object literals allow trailing commas. JSON does not. Neither do Go's encoding/json, Python's json, or Rust's serde_json in strict mode.

Fix: strip them. Or paste into a JSON Formatter — it'll point at the exact byte.

If your config file needs trailing commas (humans editing it), use JSON5 or JSONC and parse with the matching library. Don't ship JSON5 to a public API.

2. The byte-order mark (BOM)

Open a file in Notepad on Windows, save it, push it to S3, and your parser eats glass:

SyntaxError: Unexpected token  in JSON at position 0

That invisible character at byte 0 is 0xEF 0xBB 0xBF — the UTF-8 BOM. JSON.parse rejects it because RFC 8259 explicitly says implementations MUST NOT add a BOM and MAY ignore one. Node's parser doesn't ignore it.

Fix in Node:

const fs = require('fs');
let raw = fs.readFileSync('config.json', 'utf8');
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1);
const cfg = JSON.parse(raw);

Or, better, fs.readFile with encoding: 'utf8' and pipe through JSON.parse(raw.replace(/^\uFEFF/, '')).

3. Comments

{
  // dev override
  "host": "localhost"
}

JSON has no comments. Crockford explicitly removed them because people were stuffing parsing directives into them. If your tooling supports it (VS Code's settings.json, tsconfig.json), it's JSONC. If you're sending this to a server, strip them first or switch to YAML.

4. NaN, Infinity, undefined

JSON.stringify({ x: NaN })   // '{"x":null}'
JSON.stringify({ x: undefined }) // '{}'
JSON.parse('{"x": NaN}')     // SyntaxError

JSON's number type is finite. NaN and Infinity are not in the grammar. Python's json.dumps does the opposite of JavaScript and will happily emit NaN by default, which then chokes any spec-compliant parser. If you're talking Python → JS, set allow_nan=False on the Python side.

5. Single quotes

{ 'name': 'alice' }

JSON strings must be double-quoted. Single quotes are a Python/JS-ism. The error you'll see is Unexpected token ' at position 2. The fix is mechanical — sed it, or paste into the JSON Validator which highlights the offending quotes.

6. Unescaped control characters in strings

This one is sneaky. You have a log line in a string field:

{ "message": "line1\nline2" }

That looks fine — \n is a valid JSON escape. But if your producer wrote a literal newline (0x0A) into the string without escaping it, you get:

SyntaxError: Bad control character in string literal

Common culprits: printf-built JSON in bash, naive string concatenation in templating languages, log messages with embedded tabs. The fix is at the producer: use json.Marshal (Go), json.dumps (Python), JSON.stringify (JS) — never hand-roll JSON strings.

To find these in a payload:

cat suspect.json | grep -P '[\x00-\x1F]' && echo "control chars present"

7. Duplicate keys

{ "id": 1, "id": 2 }

RFC 8259 says "the names within an object SHOULD be unique" — but it doesn't forbid duplicates, and parser behavior varies. JSON.parse keeps the last value. serde_json keeps the last. Some Java parsers throw. Go's encoding/json keeps the last and silently discards the first.

This is rarely a "parse error" but it is a correctness bug. I've debugged a multi-hour outage caused by a templating engine emitting "price" twice — once in cents, once in dollars. If you suspect duplicates, run the doc through a strict validator or pipe through jq -e 'has("id")'.

8. Encoding mismatch (latin-1 vs UTF-8)

You fetched JSON from a legacy endpoint, parsed it, and got JSON.parse: bad character. Check the response headers:

Content-Type: application/json; charset=iso-8859-1

JSON is required to be UTF-8 (RFC 8259 §8.1). If the producer sent latin-1 bytes containing é (0xE9), Node's UTF-8 decoder will mark them as the replacement character or throw. Re-fetch with explicit decoding:

const buf = await fetch(url).then(r => r.arrayBuffer());
const text = new TextDecoder('iso-8859-1').decode(buf);
const data = JSON.parse(text);

Yes, you're working around someone else's bug. Open a ticket.

A debugging recipe

When you're staring at "Unexpected token at position N":

  1. Copy the byte at position N (raw.charCodeAt(N).toString(16)).
  2. Look at bytes N-3 through N+3 — context usually reveals the trap.
  3. Paste the document into a JSON Formatter — most will point at the exact character with a visual cursor.
  4. For nested or huge payloads, use a JSON Visualizer to find the malformed branch by collapsing the valid ones.

If you control the producer, validate before sending. Schema-validating with ajv (Node), pydantic (Python), or validator (Go) catches structural problems your JSON.parse would otherwise hit at runtime.

Try it

Try our free developer tools

51+ tools that run in your browser. No data sent anywhere.

Browse Tools