"Unexpected Token in JSON at Position N"
"Position 47" is not a line number, not a column, and — more often than people expect — not even the location of your actual mistake. Here's what the number really counts, why it tends to point one token past the real problem, and how to turn it into something you can actually navigate to.
// what "position" actually counts
// the exact wording depends on your JS engine, and it's less consistent than you'd expect
These are real, verified outputs from V8 (the engine behind Chrome, Node.js, and Edge) — not paraphrased:
JSON.parse('{"a": 1 "b": 2}')Expected ',' or '}' after property value in JSON at position 8 (line 1 column 9)
JSON.parse('')Unexpected end of JSON input
JSON.parse('{"a":}')Unexpected token '}', "{"a":}" is not valid JSONUnexpected token o in JSON at position 5 phrasing that's still what most people picture, and what other engines and older runtimes still produce. Don't assume there's always a number to chase down; sometimes the message is the only information you get.// why the position points past your mistake, not at it
Take the missing-comma example above: {"a": 1 "b": 2}. The reported position is 8 — the opening quote of "b". But the actual mistake, the missing comma, belongs at position 7, right after the 1. The parser can't report a problem at position 7, because nothing is wrong there yet — a value can legally be followed by whitespace. It's only when the parser reaches the next real token (the " starting "b") and finds something that isn't a comma or a closing brace that it knows something's wrong, and by then it's already one token past where the fix actually belongs.
// converting position to line and column yourself
If your engine's error doesn't already include a line and column, you can compute it the same way ToolSharp's JSON Formatter does internally: slice the string up to the reported position, count the newlines in that slice (that's the line number), and the column is the distance from the last newline to the end of the slice.
function findLineCol(text, index) { const lines = text.slice(0, index).split('\n'); return { line: lines.length, col: lines[lines.length - 1].length + 1 }; }
// what's usually actually at that position
} or ] right after the extra comma// and /* */ aren't valid JSON at all, despite being legal in JS object literals and JSONC// frequently asked questions
What does "position N" mean in a JSON parse error?
It's a plain character count from the start of the string, starting at index 0 — every character counts once, including spaces, tabs, and newlines. It is not a line number and doesn't reset at the start of each line, so for multi-line JSON it tells you almost nothing without converting it yourself.
Why does the error position point past my actual mistake?
The parser can only report a problem once it reads far enough to find a token that doesn't fit — it can't flag anything at the exact spot a comma is missing, because nothing is technically wrong there yet. For {"a": 1 "b": 2}, the missing comma belongs at position 7, but the error reports position 8, the opening quote of "b", because that's where the parser first finds something it can't make sense of.
How do I convert a JSON.parse error position to a line and column?
Slice the string up to the reported position, count the newlines in that slice for the line number, and the column is the distance from the last newline to the end of the slice: function findLineCol(text, index) { const lines = text.slice(0, index).split('\n'); return { line: lines.length, col: lines[lines.length - 1].length + 1 }; }
Why is there no position number in some JSON.parse errors?
It depends on your JS engine and the specific error. Modern V8 includes a line and column directly for some errors, but plenty of messages — like "{"a":}" is not valid JSON — carry no position at all. Don't assume there's always a number to chase down.
What usually causes "Unexpected token" in JSON?
The most common causes: a missing comma (error lands one token past the real fix), a trailing comma (error lands at the closing brace/bracket right after it), an unquoted or single-quoted key (JSON requires double quotes, no exceptions), a // or /* */ comment (not valid JSON despite being legal in JS), or an invisible character that looks like nothing's there because visually there isn't.
// try it yourself
Paste invalid JSON into the JSON Formatter and it converts the raw position into a line and column automatically — no mental arithmetic required. Where the input is repairable, Auto-fix reports exactly what it changed instead of leaving you to guess whether the fix it applied matches what you actually meant.
Not in JavaScript? The same underlying problem shows up with different wording elsewhere: Python's "JSONDecodeError: Expecting value", "Unexpected end of JSON input" when there's no document at all rather than a broken one, and PowerShell's "Invalid JSON primitive".
The position is a plain character count from the start of the string you handed to
JSON.parse— index 0 is the first character, and every character counts once, including spaces, tabs, and newlines. It is not a line number, and it doesn't reset at the start of each line. For a one-line JSON payload that distinction doesn't matter; the moment your JSON spans multiple lines (which is most real JSON), "position 312" tells you almost nothing without doing the counting yourself.