To fix a JSON syntax error, you need to find the character the parser could not match to the JSON grammar. The error message often points to where parsing *failed*, not where you made the mistake. A missing comma on line 12 can show up as an error on line 14.
JSON is defined by RFC 8259, and the standard is strict. No trailing commas, no comments, and only double-quoted keys and strings. Most errors come from writing JSON as if it were JavaScript or Python. This guide lists the errors you will hit, including the exact SyntaxError text JSON.parse throws in each browser, with the fix for each. It also explains the line and column numbers parsers report.
What counts as a JSON syntax error
JSON has a small, strict grammar. A document is a single value: an object, an array, a string, a number, true, false or null. Objects hold comma-separated "key": value pairs inside braces. Arrays hold comma-separated values inside brackets. Anything outside that grammar is a syntax error, even if it looks fine to a person.
RFC 8259 leaves no room for ambiguity, so a valid JSON document parses the same way in every conforming library. That makes the format reliable for APIs and config files. It is also why the parser won't guess what you meant.
- Keys and string values must use double quotes (
"), never single quotes or backticks. - No trailing comma after the last element of an object or array.
- No comments of any kind (
//or/* */). - No unquoted keys, and no leading zeros or
NaN/Infinityin numbers. - The document must contain exactly one top-level value, with nothing after it.
How to read line and column error messages
Every parser reports a position, but the format differs by engine. For JSON.parse in JavaScript:
- Older V8 (Chrome, Node.js) said "Unexpected token } in JSON at position 27".
- Recent V8 says "Expected double-quoted property name in JSON at position 27" and adds the line and column.
- Firefox reports "at line 1 column 28 of the JSON data".
- Safari names the problem but gives no position.
V8 positions are zero-based character offsets. Line and column numbers are one-based. Python's json module reports line, column and character offset. In every case, the number marks where the parser *stopped*, usually the first character it could not accept.
The mistake is often just before that point. The parser only notices a missing comma when it reaches the next key, so the reported column lands on that key, not on the gap where the comma belongs. Read one token back from the reported position.
import json
data = '{"name": "Ada", "age": 36,}'
try:
json.loads(data)
except json.JSONDecodeError as e:
print(f"{e.msg} at line {e.lineno} column {e.colno} (char {e.pos})")
# Python 3.12 and earlier:
# Expecting property name enclosed in double quotes at line 1 column 27 (char 26)
# Python 3.13 and later:
# Illegal trailing comma before end of object at line 1 column 26 (char 25)Here the trailing comma after 36 sits at column 26. Older Python versions report column 27, because the parser expected another key and found }. When a parser says it wanted a property name, the comma before that spot is usually the extra one. Newer versions name the trailing comma and point at it.
Common JSON syntax errors and how to fix them
Most JSON syntax errors fall into a few categories, and nearly all come from treating JSON like JavaScript or Python. Here is a quick-reference table, followed by the fixes that need more explanation.
| Mistake | Invalid | Valid |
|---|---|---|
| Trailing comma | [1, 2, 3,] | [1, 2, 3] |
| Single quotes | {'id': 1} | {"id": 1} |
| Unquoted key | {id: 1} | {"id": 1} |
| Comment | {"a": 1 // note} | {"a": 1} |
| Missing comma | {"a": 1 "b": 2} | {"a": 1, "b": 2} |
| Leading zero | {"n": 007} | {"n": 7} |
| Undefined / NaN | {"n": NaN} | {"n": null} |
| Trailing content | {"a": 1} extra | {"a": 1} |
Trailing commas
This is the most common error. JavaScript, Python and most editors accept a comma after the last element. JSON does not. Deleting one is easy, but they hide in deeply nested structures. Reformatting the document makes them easier to spot.
Single quotes and unquoted keys
JSON only accepts double quotes, for both keys and string values. Objects copied from JavaScript source or a Python dict repr use single quotes or bare identifiers, so you need to convert them. Python's str(dict) also writes True, False and None, which are not valid JSON. Use json.dumps instead, as covered in Working with JSON in Python.
Comments
JSON has no comment syntax. If a config parser accepted // comments, it was parsing JSONC, not JSON. Remove comments before you pass the text to a strict parser.
{
"id": 1,
"name": "Ada Lovelace",
"active": true,
"roles": ["admin", "editor"],
"score": 7,
"manager": null
}What "Unexpected token" and "Unexpected end of input" mean
These two messages cause most JSON.parse failures in JavaScript, and they mean opposite things. Recent V8 versions word them differently (for example, "Expected ',' or '}' after property value"), but the distinction is the same.
Unexpected token
The parser found a character that is not allowed where it is. Common causes are a stray comma, a single quote, an unquoted word like undefined, or an HTML error page (which starts with <) that your server returned instead of JSON. "Unexpected token < in JSON at position 0" almost always means the response was HTML. Check the raw response before you blame the parser.
Unexpected end of input
The parser reached the end of the string while it was still waiting for an open brace, bracket or quote to close. Either the document was cut off or a closing character is missing. You also get this when you call JSON.parse("") on an empty body, which throws "Unexpected end of JSON input."
function parseJson(text) {
try {
return { ok: true, value: JSON.parse(text) };
} catch (err) {
return { ok: false, error: err.message };
}
}
console.log(parseJson('{"a": 1,}')); // trailing comma: fails to parse
console.log(parseJson('{"a": 1')); // unclosed object: fails to parse
console.log(parseJson('{"a": 1}')); // { ok: true, value: { a: 1 } }Fixes for each JSON.parse error message
Here is each common JSON.parse message on its own, with the exact text current Chrome, Edge and Node.js print, what causes it and how to fix it. Older V8 versions printed the shorter Unexpected token X in JSON at position N form, which is listed where it differs.
"Unexpected token < in JSON at position 0"
Current V8 wording: Unexpected token '<', "<!DOCTYPE "... is not valid JSON. The text starts with <, so it is HTML, not JSON. The server sent an error page, a login redirect, a 404 page or a single-page app's index.html fallback.
const res = await fetch("/api/user")
if (!res.ok || !res.headers.get("content-type")?.includes("application/json")) {
const body = await res.text()
throw new Error(`Expected JSON, got ${res.status}: ${body.slice(0, 100)}`)
}
const user = await res.json()Fix the request (wrong URL, missing auth, wrong base path) rather than the parser. The JSON was never there.
"Unexpected end of JSON input"
The parser ran out of text before the document was complete. The usual cause is an empty string: JSON.parse(""), a 204 No Content response passed to response.json(), or a file that was never written. Check for empty text before parsing, or treat an empty body as no data.
const text = await res.text()
const data = text ? JSON.parse(text) : nullA document cut off in the middle, such as {"a": 1, usually gets a different message in current V8: Expected ',' or '}' after property value. Both mean the same thing. Make sure the whole payload arrived, and look for a missing closing } or ].
"Unexpected token u in JSON at position 0"
Current V8 wording: "undefined" is not valid JSON. You called JSON.parse(undefined). JavaScript turned undefined into the string "undefined", and the parser stopped at the u. It happens when a variable, an object property or localStorage.getItem returns nothing.
const raw = localStorage.getItem("settings")
const settings = raw ? JSON.parse(raw) : {}"Unexpected token o in JSON at position 1"
Current V8 wording: "[object Object]" is not valid JSON. You passed an object, not a string. JavaScript converted it to the text [object Object], and the parser choked on the o. The data was already parsed, often because response.json() or a library like axios did it for you. Remove the extra JSON.parse.
"Expected property name or '}' in JSON at position N"
A key is not wrapped in double quotes, as in {'name': 'Ada'} or {name: "Ada"}. This text usually comes from a JavaScript object literal or from Python's str(dict). Quote every key and string with double quotes, or generate the text with JSON.stringify or json.dumps.
{"name": "Ada"}"Expected double-quoted property name in JSON at position N"
There is a trailing comma before a closing brace, as in {"a": 1,}. After a comma the parser expects another key, finds } instead, and stops there. Delete the last comma. In an array, the same mistake ([1, 2,]) is reported as Unexpected token ']'.
"Expected ',' or '}' after property value in JSON at position N"
The parser finished a value and found something other than a comma or the end of the object. Either a comma is missing between two properties, a string contains an unescaped double quote that ended it early, or the text was cut off. Arrays get the matching message, Expected ',' or ']' after array element.
{
"title": "She said \"hi\"",
"count": 2
}"Unexpected non-whitespace character after JSON at position N"
A complete JSON value is followed by more text. Two objects back to back, an NDJSON file parsed as one document, or a stray character after the last } all cause it. Parse each line on its own for NDJSON, or remove the extra text.
const records = ndjson.split("\n").filter(Boolean).map((line) => JSON.parse(line))"Bad control character in string literal in JSON at position N"
A string contains a raw line break, tab or other control character. JSON strings must escape them as \n, \t or \u00XX. This happens when JSON is built by string concatenation. Build the object in code and serialize it with JSON.stringify, which escapes everything correctly.
"Unexpected number in JSON" and "Unexpected token" with a letter
Unexpected number usually means a number with a leading zero, such as 007, which JSON does not allow. Unexpected token followed by a letter means a bare word the grammar does not know, such as NaN, Infinity, True, None or a misspelled tru. Use 7, null, true or false instead.
Python: "Expecting value: line 1 column 1 (char 0)"
Python's version of an empty or non-JSON body. json.loads(""), an HTML error page or an empty HTTP response all raise it. Print response.status_code and response.text first, and only call response.json() when the body really is JSON.
JSON.parse SyntaxError messages by browser and runtime
Each engine words the same mistake differently, so a search for an error message often finds advice for another runtime. The tables below map the messages you will see to their cause and fix. Each one is a SyntaxError thrown by JSON.parse (or a JSONDecodeError in Python). N is the reported position.
Chrome, Edge and Node.js (V8)
Current V8 versions add the text that failed ("... is not valid JSON") or a line and column to the message. Older versions (before about Chrome 114 and Node.js 20) used the shorter "Unexpected token X in JSON at position N" form for almost everything.
| Message | Cause | Fix |
|---|---|---|
Unexpected token '<', "<!DOCTYPE "... is not valid JSON | The response is an HTML page, such as a 404, 500, login redirect or SPA fallback. Older V8: Unexpected token < in JSON at position 0. | Check response.ok and the Content-Type header before calling response.json(), and log the raw body. |
"undefined" is not valid JSON | JSON.parse(undefined). A missing variable, property or storage value was passed in. Older V8: Unexpected token u in JSON at position 0. | Guard the input: only parse when the value is a string, and fall back to a default otherwise. |
"[object Object]" is not valid JSON | An object was passed to JSON.parse, so it was converted to the string [object Object] first. Usually the data was already parsed. | Drop the extra JSON.parse, or JSON.stringify the object when storing it. |
Unexpected end of JSON input | The text is empty or cut off before a closing }, ] or quote. A 204 No Content response or a truncated file can cause this. | Check for an empty body before parsing, and make sure the whole payload arrived. |
Expected property name or '}' in JSON at position N | A key is single-quoted or unquoted, as in {'a': 1} or {a: 1}. | Wrap every key in double quotes. |
Expected double-quoted property name in JSON at position N | A trailing comma before }, as in {"a": 1,}. | Delete the comma after the last property. |
Expected ',' or '}' after property value in JSON at position N | A missing comma between properties, or an unescaped " ending a string early. | Add the comma, or escape the inner quote as \". |
Unexpected non-whitespace character after JSON at position N | Extra content after a complete value. Two documents back to back, NDJSON parsed as one document, or a stray character. | Parse one document at a time (split NDJSON by line) or remove the trailing text. |
Bad control character in string literal in JSON at position N | A raw newline, tab or other control character inside a string. | Escape it as \n, \t or \u00XX, ideally by generating the JSON with JSON.stringify. |
Firefox (SpiderMonkey)
Firefox starts every message with JSON.parse: and ends it with at line L column C of the JSON data, so the location is easy to find.
| Message | Cause | Fix |
|---|---|---|
JSON.parse: unexpected character at line 1 column 1 of the JSON data | The text does not start with a JSON value. It is usually HTML, undefined or plain text. | Inspect the raw response and check that the endpoint returns JSON. |
JSON.parse: expected property name or '}' | Single-quoted or unquoted key. | Use double-quoted keys. |
JSON.parse: expected double-quoted property name | Trailing comma before }. | Remove the trailing comma. |
JSON.parse: unexpected end of data | Empty or truncated input. | Check for an empty body and make sure the payload is complete. |
JSON.parse: unexpected non-whitespace character after JSON data | Extra content after the first complete value. | Parse each document separately or remove the extra text. |
JSON.parse: bad control character in string literal | Raw newline or tab inside a string. | Escape the character. |
Safari (JavaScriptCore)
Safari's messages start with JSON Parse error: and include no position. Paste the text into a validator to find the line and column.
| Message | Cause | Fix |
|---|---|---|
JSON Parse error: Unrecognized token '<' | HTML instead of JSON. | Check the response status and body. |
JSON Parse error: Unexpected identifier "undefined" | JSON.parse(undefined). | Guard the input before parsing. |
JSON Parse error: Unexpected EOF | Empty or truncated input. | Check for an empty body. |
JSON Parse error: Single quotes (') are not allowed in JSON | Single-quoted key or string. | Switch to double quotes. |
JSON Parse error: Property name must be a string literal | Trailing comma before }, or an unquoted key. | Remove the comma or quote the key. |
Python (json.decoder.JSONDecodeError)
Python's json.loads and json.load raise json.decoder.JSONDecodeError (also available as json.JSONDecodeError). It is a subclass of ValueError, and the message includes the line, column and character offset. In the requests library, response.json() raises the same kind of error when a response body is not JSON.
| Message | Cause | Fix |
|---|---|---|
Expecting value: line 1 column 1 (char 0) | Empty string, HTML or other non-JSON text. Often an error page or an empty HTTP response. | Print response.status_code and response.text before decoding. |
Expecting property name enclosed in double quotes | Single-quoted keys, typically from str(dict) output, or a trailing comma on Python 3.12 and earlier. | Produce the text with json.dumps instead of str(), or remove the trailing comma. |
Illegal trailing comma before end of object / array | A trailing comma, as reported by Python 3.13 and later. | Remove the comma. |
Expecting ',' delimiter | A missing comma between items, or an unescaped quote inside a string. | Add the comma or escape the quote. |
Extra data: line 1 column N | More than one JSON value in the text, such as NDJSON. | Decode line by line with json.loads per line. |
Invalid control character at: line L column C | A raw newline or tab inside a string. | Escape it, or pass strict=False to accept control characters inside strings. |
Unterminated string starting at: line L column C | A string is missing its closing quote, often because the text was truncated. | Close the string or fetch the complete payload. |
Invisible errors: BOM, encoding and escapes
Some errors are hard to see because the bad character is invisible or looks fine.
Byte-order mark (BOM)
A file saved as "UTF-8 with BOM" starts with the bytes EF BB BF. RFC 8259 says JSON generators must not add one. Parsers may ignore it, but many treat it as an unexpected character at position 0, and you get a confusing error on a document that looks correct. Save the file as plain UTF-8 without a BOM, or strip those bytes before parsing.
Unescaped control and special characters
Inside a string, you must escape double quotes, backslashes and control characters such as literal newlines or tabs. A raw line break pasted into a string value is invalid. Write it as \n. Embedding one JSON document inside another string makes escaping tricky. See How to escape and unescape JSON strings.
{
"path": "C:\\Users\\ada\\data.json",
"quote": "She said \"hello\" today",
"multiline": "line one\nline two"
}Duplicate keys
RFC 8259 does not ban duplicate keys, but it says the result is unpredictable. Most parsers keep the last value without warning, so {"a": 1, "a": 2} becomes {"a": 2} and data is lost with no error. Some strict validators flag it. Treat duplicate keys as a bug even when nothing complains.
A fast workflow to find and fix JSON errors
In a large or minified document, you can't find a missing comma by eye. Use this loop instead:
- Paste the raw text into a validator to get the line and column of the first error.
- Reformat the document so nesting and commas sit on their own lines.
- Fix the character at (or just before) the reported position.
- Validate again. Parsers report one error at a time, so repeat until it passes.
Once the JSON parses, run it through a formatter. Structural mistakes, like a missing comma between two objects, become easy to see, and you get a clean, indented copy to commit.