DEV Community

mergejsonfiles
mergejsonfiles

Posted on

Every JSON error message, decoded

JSON parsers are strict, and their error messages are famously unhelpful. You get a position number and a vague noun. The actual mistake is usually somewhere else entirely.

This is a reference for the messages you actually see, what causes each one, and — the part nobody tells you — why the position they report is often a lie.

Why the position is usually wrong
Start here, because it explains most of the confusion.

A parser reports where it gave up, not where you made the mistake. Those are different places, sometimes by hundreds of lines.

{
"a": 1,
"b": 2,
}
The mistake is the comma after 2. The parser is fine with that comma at the time — a comma legitimately means "another key is coming". It only fails when it reaches } and finds no key. So it blames the }, one character past the real problem.

The rule: the error position is where the parser's expectation broke, which is at or after your typo, never before. So when you get a position, look backwards from it.

The messages
Unexpected token } in JSON at position N
Almost always a trailing comma — the case above. Look at the character just before position N. If it's a comma, that's your bug.

Same message with ] instead of } is a trailing comma in an array.

Unexpected end of JSON input
The document ended while something was still open. Two very different causes:

An unclosed bracket or brace. Every { needs }, every [ needs ].
Truncation. The file or response is genuinely incomplete — a fetch that failed halfway, a log line cut at a length limit, or an LLM that hit its token cap mid-object.
Worth distinguishing, because cause 2 means data is missing, not malformed. Adding a } to make it parse gives you valid JSON with a chunk of your records gone.

Special case: this is also what you get from JSON.parse(""). An empty string is not valid JSON. If you're parsing a fetch response, an empty body throws exactly this — and the real problem is a 204 or a failed request, not your JSON.

Unexpected token ' in JSON at position 1
Single quotes. That's a Python dict or a JS object literal, not JSON:

{'name': 'Ada'}
JSON requires double quotes on both keys and string values. No exceptions, no config flag.

Unexpected token o in JSON at position 1
My favourite, because it looks cryptic and isn't. You did this:

JSON.parse(someObject)
someObject is already an object. JSON.parse needs a string, so JS coerces it — giving "[object Object]". The parser reads [, expects a value, and finds o.

Your JSON is fine. You just parsed something that was never a string. Usually this means a library (axios, most fetch wrappers) already parsed it for you.

The [object Object] tell also shows up as Unexpected token o at position 1 in a lot of stack traces where people spend an hour debugging their payload. Don't.

Unexpected token < in JSON at position 0
The server sent you HTML, not JSON. That < is the start of <!DOCTYPE html> or .

You are parsing an error page. A 404, a 500, a login redirect, a Cloudflare challenge, or a proxy's error output. Log the raw response text and you'll see it instantly. Nothing about your parsing code is broken — check the status code before you parse.

Unexpected token n in JSON at position N
Usually NaN, sometimes None, occasionally a bare nan. JSON has null and nothing else for absence. NaN and Infinity are valid JavaScript numbers and valid Python floats but they are not valid JSON — which is why JSON.stringify({x: NaN}) quietly gives you {"x":null}.

None specifically means Python got involved somewhere — someone str()'d a dict instead of json.dumps'ing it.

Unexpected token T in JSON at position N
True or False with a capital letter. Python again. JSON booleans are lowercase.

Expecting property name enclosed in double quotes (Python)
Python's version of the trailing comma error, and it's clearer than JavaScript's — it tells you it wanted a key and didn't get one. Same fix: look for the comma before the closing brace.

It also fires for unquoted keys:

{name: "Ada"}
Keys are strings. They get quotes.

Expecting value: line 1 column 1 (char 0) (Python)
Python's equivalent of the < and empty-string cases. The content isn't JSON at all — it's HTML, it's empty, or it's a plain-text error. Print the first 200 characters of the raw response.

Extra data: line 2 column 1 (char N) (Python)
This one gets misdiagnosed constantly. Your file looks like this:

{"id": 1, "status": "shipped"}
{"id": 2, "status": "pending"}
That file is not broken. It's JSONL (also called NDJSON) — one JSON document per line, which is what log shippers, BigQuery exports and streaming APIs emit, because it appends without rewriting and streams without buffering.

It is not one JSON document, so a whole-document parser reads line 1 successfully, finds more content, and complains. Parse it line by line:

with open("data.jsonl") as f:
rows = [json.loads(line) for line in f if line.strip()]
Or in pandas: pd.read_json("data.jsonl", lines=True).

Bad control character in string literal
A raw newline or tab inside a string. JSON strings can't contain literal control characters — they must be escaped as \n, \t, \r.

This is what happens when someone builds JSON with string concatenation and interpolates a multi-line value. Which is the actual lesson: don't build JSON by hand. JSON.stringify and json.dumps exist and they escape correctly.

Invalid \escape
A lone backslash. The classic is a Windows path:

{"path": "C:\Users\ada"}
\U isn't a valid escape sequence. JSON knows \", \, \/, \b, \f, \n, \r, \t, and \uXXXX. Everything else is an error. The fix is C:\Users\ada.

No error at all — and that's the bug
{"id": 1, "id": 2}
Duplicate keys. This parses. The spec says the behaviour is undefined; in practice almost every parser silently keeps the last value and discards the first.

So it never throws, you never see a warning, and a field just quietly has the wrong value. This is the worst one on the list precisely because it isn't an error.

The three-step debug
Look backwards from the reported position, not at it. The parser blames the character where it gave up.
Print the raw string before parsing. Half of all "JSON errors" are HTML error pages, empty bodies, or already-parsed objects. Neither is a JSON problem.
Then use a validator that gives you line and column rather than a byte offset, because counting to character 4,721 by hand is not a debugging strategy.
For step 3 I use a JSON validator that points at the exact line. It runs in the browser, which matters more than it sounds — the JSON you're debugging is by definition the real payload, usually with a token or a customer record in it, and pasting that into a server-side tool is how it ends up somewhere you didn't intend. (Some of those tools have share links that default to public. Check before you paste.)

If the JSON is broken in the mechanical ways above rather than genuinely malformed — fences, trailing commas, True/None, single quotes — https://mergejsonfiles.com/json-repair fixes those and shows a log of what it changed. The log matters for the truncation case: you want to know when "fixed" meant "closed the brackets around incomplete data".

And when the document is valid but unreadable, a formatter is the fastest validator you have anyway. If it won't format, it won't parse.

The short version
Message Real cause
Unexpected token } Trailing comma
Unexpected end of JSON input Unclosed bracket, or truncated/empty data
Unexpected token ' Single quotes
Unexpected token o at position 1 You parsed an object, not a string
Unexpected token < It's an HTML error page
Unexpected token n / T NaN/None/True — Python leaked in
Expecting property name... Trailing comma or unquoted key
Expecting value: line 1 column 1 Not JSON at all — check the raw response
Extra data: line 2 It's JSONL, parse line by line
Bad control character Literal newline in a string
Invalid \escape Unescaped Windows path
(no error) Duplicate keys — last one silently wins
What's the worst JSON error message you've had to decode? The <!DOCTYPE one got me for a solid twenty minutes once.

Top comments (0)