Skip to content
Tools

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 error

The messages at a glance

MessageWhat it meansMost likely cause
Expecting valueNo JSON value where one must startEmpty text, HTML, True/None, a trailing comma in an array
Expecting property name enclosed in double quotesInside {, the key is not a double-quoted stringSingle quotes, unquoted keys, a trailing comma in an object (before 3.13)
Extra dataA complete value was followed by more textSeveral JSON documents or JSON Lines in one string
Expecting ',' delimiterTwo items with no comma, or the text stops earlyMissing comma, unescaped quote, truncated text
Expecting ':' delimiterA key with no colon after itTypo, or = instead of :
Unterminated string starting atA string never closesTruncation, or an unescaped " inside a string
Invalid control character atA raw tab or newline inside a stringMulti-line text pasted into a value
Invalid \escapeA backslash followed by a letter JSON does not allowWindows paths, regexes
Illegal trailing comma before end of object / arrayA 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+FEFFA 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:

  1. An empty HTTP body. A 204 response, an empty error response, or a request that failed silently. requests raises requests.exceptions.JSONDecodeError (a subclass of both json.JSONDecodeError and the requests exception base) with this exact message.
  2. 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.
  3. A file read twice. f.read() followed by json.load(f) leaves the file pointer at the end, so json.load sees an empty string. Call f.seek(0) or read once.
  4. An empty or missing file, including one that another process is still writing.
  5. Python literals: json.loads("True") or json.loads("None") fail here too, because JSON spells them true and null.
Fails with a message that tells you what came back, instead of a bare JSONDecodeError.
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

TypeError covers non-string input such as None. Note that Python accepts NaN and Infinity by default, which strict JSON parsers in other languages reject.
import json

def is_json(text: str) -> bool:
    try:
        json.loads(text)
    except (json.JSONDecodeError, TypeError):
        return False
    return True

For 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.

More JSON guides