Python JSONDecodeError Explained: Expecting Value, Extra Data and More
· 6 min read
json.decoder.JSONDecodeError is the ValueError Python raises when json.loads, json.load or requests' response.json() gets text that is not valid JSON. Expecting value: line 1 column 1 (char 0) means the text is empty or does not start with JSON at all (often HTML); the other messages name the exact rule that was broken at that position. Paste the text into the JSON validator to see the spot highlighted, then use the fix for your message below.
Every message ends with the location in two forms: a 1-based line and column, and a 0-based char offset into the whole string. The exception also carries them as attributes, which is the fastest way to print the surrounding text.
import json
text = '{"name": "Ada", "langs": ["en", "fr",]}'
try:
data = json.loads(text)
except json.JSONDecodeError as e:
print(e.msg, e.lineno, e.colno, e.pos)
print(repr(e.doc[max(0, e.pos - 20):e.pos + 20])) # context around the errorThe messages at a glance
| Message | What it means | Most likely cause |
|---|---|---|
Expecting value | No JSON value where one must start | Empty text, HTML, True/None, a trailing comma in an array |
Expecting property name enclosed in double quotes | Inside {, the key is not a double-quoted string | Single quotes, unquoted keys, a trailing comma in an object (before 3.13) |
Extra data | A complete value was followed by more text | Several JSON documents or JSON Lines in one string |
Expecting ',' delimiter | Two items with no comma, or the text stops early | Missing comma, unescaped quote, truncated text |
Expecting ':' delimiter | A key with no colon after it | Typo, or = instead of : |
Unterminated string starting at | A string never closes | Truncation, or an unescaped " inside a string |
Invalid control character at | A raw tab or newline inside a string | Multi-line text pasted into a value |
Invalid \escape | A backslash followed by a letter JSON does not allow | Windows paths, regexes |
Illegal trailing comma before end of object / array | A comma right before } or ] | Hand-edited JSON (message added in Python 3.13) |
Unexpected UTF-8 BOM (decode using utf-8-sig) | The string starts with U+FEFF | A file saved with a byte order mark |
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Position 0 means the very first character is wrong, or there is no character at all. Check len(text) and text[:200] before anything else. The causes, most common first:
- An empty HTTP body. A 204 response, an empty error response, or a request that failed silently.
requestsraisesrequests.exceptions.JSONDecodeError(a subclass of bothjson.JSONDecodeErrorand the requests exception base) with this exact message. - An HTML page. A 404, a login redirect or a 500 error page. The JavaScript version of this bug is Unexpected token '<', "<!DOCTYPE", and the fix is the same: check the status and content type.
- A file read twice.
f.read()followed byjson.load(f)leaves the file pointer at the end, sojson.loadsees an empty string. Callf.seek(0)or read once. - An empty or missing file, including one that another process is still writing.
- Python literals:
json.loads("True")orjson.loads("None")fail here too, because JSON spells themtrueandnull.
import requests
r = requests.get(url, headers={"Accept": "application/json"}, timeout=10)
r.raise_for_status() # 4xx/5xx raise HTTPError with the status
ctype = r.headers.get("Content-Type", "")
if r.status_code == 204 or not r.content:
data = None
elif "json" not in ctype:
raise ValueError(f"Expected JSON, got {ctype!r}: {r.text[:200]!r}")
else:
data = r.json()JSONDecodeError: Expecting property name enclosed in double quotes
json.loads("{'a': 1}") fails with Expecting property name enclosed in double quotes: line 1 column 2 (char 1). Column 2 is the single quote. This text is almost always the output of print(some_dict) or str(some_dict), which is Python syntax, not JSON. Fix the producer with json.dumps; if you only have the string, Python dict to JSON with single quotes covers ast.literal_eval and its limits.
On Python 3.12 and earlier, a trailing comma in an object ({"a": 1,}) also produces this message, because the comma promised another key. Python 3.13 reports it as Illegal trailing comma before end of object instead.
JSONDecodeError: Extra data
The parser read one full value and found more non-whitespace text after it. json.loads('{"a": 1}\n{"b": 2}') raises Extra data: line 2 column 1 (char 9). That is JSON Lines (NDJSON): one document per line. Parse each line on its own:
import json
with open("events.jsonl", encoding="utf-8") as f:
events = [json.loads(line) for line in f if line.strip()]If the extra text is junk rather than another document (a log line printed after the body, a stray brace), json.JSONDecoder().raw_decode(text) returns the first value and the index where it ended, so you can inspect what follows. More on the format in what NDJSON is.
Expecting ',' delimiter and Expecting ':' delimiter
Expecting ',' delimiter appears in two situations. Inside complete text, two items sit side by side without a comma, or an unescaped " inside a string ended it early so the rest looks like a new token. At the end of the text, the document was cut off: json.loads('{"a": 1') gives Expecting ',' delimiter: line 1 column 8 (char 7), where char 7 is the length of the string. Expecting ':' delimiter means a key is not followed by a colon, for example {"a" 1}.
Invalid control character and Invalid \escape
JSON strings cannot contain raw characters below U+0020. A literal tab or newline inside a value raises Invalid control character at: line 1 column 9 (char 8). The right fix is to escape them at the source by producing the JSON with json.dumps. If you must read such data, json.loads(text, strict=False) accepts control characters inside strings; it does not relax any other rule.
Invalid \escape usually comes from Windows paths or regular expressions: {"path": "C:\data"} contains \d, which is not a JSON escape. The producer should write \\. Do not "fix" it with a blanket string replace on the consumer side, because valid escapes like \n would break.
The UTF-8 BOM error
Files saved by some Windows tools start with a byte order mark. json.loads on such a string raises Unexpected UTF-8 BOM (decode using utf-8-sig), and the message tells you the fix: open the file with encoding="utf-8-sig", which strips the mark if present and is harmless if not.
Check whether a string is valid JSON
import json
def is_json(text: str) -> bool:
try:
json.loads(text)
except (json.JSONDecodeError, TypeError):
return False
return TrueFor syntax mistakes in hand-edited files (single quotes, trailing commas, True/None, comments), the JSON fixer repairs them in the browser and shows a diff. Once the JSON is valid, JSON to Python generates dataclasses or Pydantic models for it. Other error families are listed in the common JSON errors hub.
FAQ
What does "JSONDecodeError: Expecting value: line 1 column 1 (char 0)" mean?
The text is empty or its first character cannot start a JSON value. Print len(text) and text[:200]: an empty string points to an empty response or file, and < points to an HTML page.
Why does json.loads fail on a Python dict string?
str(dict) produces Python syntax with single quotes and True/None, which is not JSON. Use json.dumps to produce JSON, or ast.literal_eval to read an existing dict string.
What does JSONDecodeError "Extra data" mean?
One complete JSON value was followed by more text, usually several documents or JSON Lines in one string. Parse line by line, or wrap the values in an array.
How do I check if a string is valid JSON in Python?
Call json.loads inside try/except json.JSONDecodeError. Catch TypeError too if the input might not be a string.
Is requests.exceptions.JSONDecodeError different from json.JSONDecodeError?
Since requests 2.27, response.json() raises requests.exceptions.JSONDecodeError, which subclasses json.JSONDecodeError, so catching either works. The message and position are the same.

