Fix JSON Unexpected Token Error: Decode the Message

You paste a payload, call JSON.parse, and get SyntaxError: Unexpected token } in JSON at position 27. You jump to character 27, stare at a perfectly innocent closing brace, and find nothing wrong. That confusion is the single biggest reason these errors waste time. The position the parser reports is where it gave up, not where you made the mistake. The actual bug is almost always one or two characters earlier. Once you internalize that, every JSON syntax error becomes a quick lookup instead of a hunt.

This is a decode-the-message guide. Find your exact error string below, learn what really triggered it, then use the line-finding workflow at the end to fix it in seconds.

Why the reported position lies to you

A JSON parser reads left to right and only fails when it hits a character that cannot legally follow what it has seen so far. With a trailing comma like {"a": 1,}, the comma is technically fine in the moment, because another key could follow. The parser only chokes when it reaches the } and discovers there is no next key. So it blames the brace, even though your real error is the comma right before it.

The rule to remember: read backward from the reported position. The token named in the message is innocent more often than not. The mistake is the thing immediately preceding it.

JavaScript: Unexpected token errors decoded

Modern V8 (Chrome and Node) changed its wording, so you may see either of two formats. Older builds say Unexpected token } in JSON at position N. Newer builds say Unexpected token 'X', "...snippet..." is not valid JSON and quote a snippet instead of an index. Both point at the same kinds of problems.

  • Unexpected token } or ] — A trailing comma. The error sits on the closing bracket; delete the comma just before it. JSON forbids trailing commas entirely, unlike JavaScript and many config formats.
  • Unexpected token at a property name (a string key) — A missing comma between the previous value and this key. The parser expected a comma or a closing brace and found a fresh string instead, so it reports the new key, not the gap before it.
  • Unexpected token ' — Single quotes. JSON only allows double-quoted strings and keys. This is JavaScript object syntax, not JSON.
  • Unexpected token (a bare word like n or t) — An unquoted key, or a stray value like undefined, NaN, or a trailing comment. JSON has no comments and no undefined.
  • Unexpected token '<' ... is not valid JSON — You are parsing HTML, not JSON. The server returned an error page or a login redirect and your code fed it straight into the parser. Log the raw response before parsing.

Firefox (SpiderMonkey) words these differently, and the exact text often tells you the cause. A trailing comma reports as JSON.parse: unexpected character (it points at the closing bracket that followed the comma). An unquoted or single-quoted key reports as JSON.parse: expected property name or '}'. And when extra junk follows an otherwise complete object, you get JSON.parse: unexpected non-whitespace character after JSON data — a classic sign of a doubled response or two concatenated JSON values. Per MDN's SyntaxError reference, the exact wording is engine-specific, so never match on the string in code — only use it to locate the bug.

Python: Expecting value and friends

Python's json module gives you a line, column, and character index, which is more useful than a bare position. Decode the common ones:

  • Expecting value: line 1 column 1 (char 0) — The parser found nothing valid at the very start. Usually the input is empty, is None, or is an HTTP body from a 204/404/HTML response. Print repr(text) first — you will often see '' or '<!DOCTYPE html>'.
  • Expecting property name enclosed in double quotes — A single-quoted key, an unquoted key, or a trailing comma inside an object. The classic cause is json.loads(str(my_dict)): Python's str() renders dicts with single quotes, which is not JSON. If you genuinely have a Python literal, use ast.literal_eval() instead; if you have a real dict, you do not need to parse it at all.
  • Expecting ',' delimiter — A missing comma between two items, or an unescaped double quote inside a string value that ends the string early. The column points at where the parser expected the comma, so the gap is just before it.

The paste-into-the-formatter workflow

Raw error positions are hard to map to a line in a long, single-line payload. The fastest reliable fix is to let a formatter find the line for you:

  1. Copy the entire offending payload, including the broken part.
  2. Paste it into a JSON formatter. A good one pretty-prints valid JSON and, when it fails, reports the error against the readable multi-line version — so the line number actually means something.
  3. Jump to the reported line, then look at the line above it. Trailing commas and missing commas almost always reveal themselves there.
  4. If the payload contains escaped JSON-inside-a-string (a common API pattern), run it through a string escape and unescape tool first to unwrap the inner JSON before formatting it.

Everything happens in your browser — nothing is uploaded. That matters when the suspect payload contains tokens, customer records, or anything you should not paste into a random server-side validator.

Binary-search isolation for huge files

When a multi-megabyte file fails and the position is somewhere in the middle, do not read it line by line. Bisect it:

  1. Delete the bottom half of the array's elements (keep brackets balanced) and re-parse.
  2. If it now parses, the bug was in the half you removed; if it still fails, the bug is in the half you kept.
  3. Halve the suspect region again and repeat. A million-line file narrows to one bad record in around 20 cuts.

To compare a known-good version against the broken one, drop both into a diff checker — an editor's auto-insert or a bad merge often shows up as a one-character change you would never spot by eye. For more on reading the messages parsers and runtimes throw at you, see how to read a stack trace.

Prevent the next one

Most of these errors trace back to three habits: hand-editing JSON (commas drift), reusing JavaScript or Python object literals as JSON (quotes and undefined sneak in), and parsing HTTP responses without checking the status or content type first. Always serialize with a real encoder (JSON.stringify, json.dumps) rather than string-building, and guard your parse calls so an HTML error page never reaches the parser. The JSON grammar is tiny — double quotes only, no trailing commas, no comments — and following it strictly makes these errors disappear.

Frequently Asked Questions

Because the position is where the parser stopped, not where you made the mistake. A parser only fails when it hits a character that cannot legally follow what it has read. A trailing comma, for example, is reported at the closing bracket, since the parser only realizes no next key is coming once it reaches the brace. Always read one or two characters backward.

Char 0 means the parser found nothing valid at the start, so the input is usually empty, None, or not JSON at all. Print repr(your_text) before parsing. You will often see an empty string or HTML like '', which means an API returned an error page or empty body instead of JSON. Check the HTTP status and content type first.

The JSON specification permits only double-quoted strings and keys, so {'a': 1} is JavaScript or Python object syntax, not JSON. This commonly happens when you call json.loads(str(my_dict)) in Python, because str() renders dicts with single quotes. Use json.dumps() to produce valid JSON, or ast.literal_eval() if you genuinely have a Python literal string.

You are parsing HTML, not JSON. The < is the start of a tag like or . Your HTTP request returned an error page, a login redirect, or a 404 page, and that response was passed straight into JSON.parse. Log the raw response body and check the status code before parsing anything.

Use binary search instead of scanning line by line. Delete the bottom half of the array's elements while keeping the brackets balanced, then re-parse. If it parses, the bug was in the removed half; if not, it is in the half you kept. Halve the suspect region repeatedly. A million-line file narrows down in about 20 cuts.