Python JSON error handling: every JSONDecodeError message (tested)
Python's json module raises JSONDecodeError with about a dozen different messages, accepts some invalid JSON without complaint, and uses plain TypeError when writing. We measured each case in Python 3.13 (and 3.11 where they differ) and wrote a parse helper that shows the text around the error.
1. Every common JSONDecodeError message and what causes it
json.JSONDecodeError is a subclass of ValueError, so except ValueError catches it too. We fed json.loads() the ten broken inputs that come up most and printed e.msg with the position:
import json
print("JSONDecodeError ->", " > ".join(c.__name__ for c in json.JSONDecodeError.__mro__[1:-2]))
cases = [
("empty string", ''),
("HTML error page", '<html>502 Bad Gateway</html>'),
("single quotes", "{'a': 1}"),
("trailing comma", '{"a": 1,}'),
("missing comma", '{"a": 1 "b": 2}'),
("unterminated string", '{"a": "hi}'),
("raw newline in string", '{"a": "line1\nline2"}'),
("two documents", '{"a": 1}{"b": 2}'),
("UTF-8 BOM", '{"a": 1}'),
("Python True/None", '{"a": True, "b": None}'),
]
for label, text in cases:
try:
json.loads(text); print(f"{label:22} OK")
except json.JSONDecodeError as e:
print(f"{label:22} {e.msg} (line {e.lineno} col {e.colno})")
Output:
JSONDecodeError -> ValueError > Exception
empty string Expecting value (line 1 col 1)
HTML error page Expecting value (line 1 col 1)
single quotes Expecting property name enclosed in double quotes (line 1 col 2)
trailing comma Illegal trailing comma before end of object (line 1 col 8)
missing comma Expecting ',' delimiter (line 1 col 9)
unterminated string Unterminated string starting at (line 1 col 7)
raw newline in string Invalid control character at (line 1 col 13)
two documents Extra data (line 1 col 9)
UTF-8 BOM Unexpected UTF-8 BOM (decode using utf-8-sig) (line 1 col 1)
Python True/None Expecting value (line 1 col 7)
Version note: the trailing-comma message is new in Python 3.13. On Python 3.11 (we ran the same file in python:3.11-alpine) the same input gives Expecting property name enclosed in double quotes (line 1 col 9), the same message as single quotes. So an older Python reporting "double quotes" may really mean a trailing comma.
Deep dives for each message: Expecting value: line 1 column 1 (char 0) (empty body, HTML error page, Python literals), Expecting property name enclosed in double quotes, Invalid control character, Extra data. For the BOM, open the file with encoding="utf-8-sig" as the message says.
2. What json accepts without any error
Some invalid JSON does not raise, which matters if other systems will read your data:
import json, math
print(json.loads('{"a": NaN, "b": Infinity}')) # accepted by default
print(json.dumps({"x": math.nan})) # written by default
def strict_const(name):
raise ValueError(f"non-standard JSON constant {name}")
try:
json.loads('{"a": NaN}', parse_constant=strict_const)
except ValueError as e:
print("parse_constant:", e)
try:
json.dumps({"x": math.nan}, allow_nan=False)
except ValueError as e:
print("allow_nan=False:", e)
print(json.loads('{"a": 1, "a": 2}')) # duplicate key: last wins
Output:
{'a': nan, 'b': inf}
{"x": NaN}
parse_constant: non-standard JSON constant NaN
allow_nan=False: Out of range float values are not JSON compliant: nan
{'a': 2}
NaN and Infinity are not valid JSON, but Python reads and writes them by default, and a JavaScript JSON.parse on the other end will fail. Use allow_nan=False when writing and parse_constant when reading if you need strict JSON. Duplicate keys are silently resolved to the last value.
3. A parse helper that says where the JSON broke
The exception has msg, doc, pos, lineno and colno, so you can show the text around the error instead of only "line 1 column 26":
import json
def parse(text, source="input"):
try:
return json.loads(text)
except json.JSONDecodeError as e:
start = max(e.pos - 20, 0)
snippet = e.doc[start:e.pos + 20]
raise ValueError(f"{source}: {e.msg} at line {e.lineno} col {e.colno}: "
f"...{snippet!r}...") from None
for text in ['{"user": "ann", "age": 31,}', '<html>502 Bad Gateway</html>']:
try:
parse(text, "api response")
except ValueError as e:
print(e)
data = parse('{"user": "ann"}')
print("missing key with .get():", data.get("age"))
try:
data["age"]
except KeyError as e:
print("missing key with []:", type(e).__name__, e)
Output:
api response: Illegal trailing comma before end of object at line 1 col 26: ...'r": "ann", "age": 31,}'...
api response: Expecting value at line 1 col 1: ...'<html>502 Bad Gatewa'...
missing key with .get(): None
missing key with []: KeyError 'age'
The snippet makes the second case obvious: the API returned an HTML error page, not JSON. Catch JSONDecodeError only around the parse call, and handle missing keys separately: a KeyError after a successful parse is a different problem (the JSON is valid, it just doesn't have that field). Use .get() for optional fields.
4. The encoding side: TypeError is not JSON serializable
json.dumps() raises TypeError, not a JSON-specific exception, for any type it doesn't know:
import json, datetime, decimal
for value in [{1, 2}, datetime.date(2026, 10, 22), decimal.Decimal("1.10"), b"raw"]:
try:
json.dumps(value)
except TypeError as e:
print(f"{type(value).__name__:8} TypeError: {e}")
def default(o):
if isinstance(o, (datetime.date, datetime.datetime)): return o.isoformat()
if isinstance(o, decimal.Decimal): return str(o)
if isinstance(o, set): return sorted(o)
raise TypeError(f"can't serialize {type(o).__name__}")
print(json.dumps({"d": datetime.date(2026, 10, 22), "p": decimal.Decimal("1.10"), "s": {2, 1}}, default=default))
Output:
set TypeError: Object of type set is not JSON serializable
date TypeError: Object of type date is not JSON serializable
Decimal TypeError: Object of type Decimal is not JSON serializable
bytes TypeError: Object of type bytes is not JSON serializable
{"d": "2026-10-22", "p": "1.10", "s": [1, 2]}
A default= function converts the types you know and must raise TypeError for the rest, so real bugs still surface. More in our Object of type X is not JSON serializable guide and the numpy int64 version.
Frequently asked questions
How do I handle JSON errors in Python?
Wrap json.loads/json.load in try/except json.JSONDecodeError (a ValueError subclass) and use e.msg, e.lineno, e.colno and e.doc[e.pos-20:e.pos+20] to report where it broke. json.dumps raises TypeError for unsupported types.
Is JSONDecodeError a ValueError?
Yes. json.JSONDecodeError subclasses ValueError, so except ValueError also catches it.
Why does Python json accept NaN?
json.loads and json.dumps allow NaN, Infinity and -Infinity by default even though they are not valid JSON. Pass allow_nan=False to dumps, and a parse_constant function to loads to reject them.
What does Illegal trailing comma before end of object mean?
A comma after the last item, like {"a": 1,}. Python 3.13 added this message; Python 3.11 reports the same input as Expecting property name enclosed in double quotes.
Written with AI assistance. Measured on 2026-10-22 on Debian with Python 3.13.5 (json module), with the decode-message table also run on Python 3.11.17 (python:3.11-alpine) to compare the trailing-comma message. Every block was run verbatim and its output pasted unedited. PlainHeaders is our extension; Python is free.