json.JSONDecodeError is raised by json.loads() when the input is not valid JSON. It is a subclass of ValueError and includes the line and column of the error. This guide walks through every common cause and the exact fix for each.
What is JSONDecodeError?
Introduced in Python 3.5, json.JSONDecodeError is raised whenever json.loads() receives a string that doesn't conform to the JSON specification. You can catch it as eitherjson.JSONDecodeError orValueError.
import json
try:
data = json.loads("not valid json")
except json.JSONDecodeError as e:
print(e.msg) # Expecting value
print(e.lineno) # 1
print(e.colno) # 1
print(e.pos) # 0Cause 1: HTML Response Instead of JSON
The most common cause. The server returns an HTML error page (e.g., a 404 or 500) instead of JSON. Always check the status code and Content-Type before parsing.
Broken — crashes when server returns HTML:
import requests
response = requests.get("https://api.example.com/data")
data = response.json() # JSONDecodeError if response is HTMLFixed — validate before parsing:
import requests
response = requests.get("https://api.example.com/data")
# Check status first
response.raise_for_status()
# Check Content-Type
content_type = response.headers.get("Content-Type", "")
if "application/json" not in content_type:
raise ValueError(f"Expected JSON, got: {content_type}\n{response.text[:200]}")
data = response.json()Cause 2: Empty Response Body
Some endpoints return an empty 200 OK response. Calling json.loads("") raises "Expecting value: line 1 column 1".
Broken:
import json text = "" data = json.loads(text) # JSONDecodeError: Expecting value: line 1 column 1
Fixed:
import json
def safe_json_loads(text: str, fallback=None):
if not text or not text.strip():
return fallback
return json.loads(text)
data = safe_json_loads(response.text, fallback={})Cause 3: BOM (Byte Order Mark) Characters
Files saved by Windows editors sometimes start with a UTF-8 BOM (\ufeff). JSON parsers reject this invisible character at the start of the string.
Broken:
with open("data.json", encoding="utf-8") as f:
data = json.load(f) # JSONDecodeError if file has BOMFixed — two approaches:
# Option 1: Strip BOM from string
text = response.text.lstrip('')
data = json.loads(text)
# Option 2: Open file with utf-8-sig encoding (strips BOM automatically)
with open("data.json", encoding="utf-8-sig") as f:
data = json.load(f)Cause 4: Single Quotes Instead of Double Quotes
Python dicts use single quotes when printed, but JSON requires double quotes. If you paste astr(dict) output into json.loads(), it will fail.
Broken:
# Python dict printed to string — single quotes, not valid JSON
text = "{'name': 'Alice', 'age': 30}"
data = json.loads(text) # JSONDecodeError: Expecting property nameFixed — use json.dumps() to serialize Python objects:
# Serialize Python dict to proper JSON string first
d = {"name": "Alice", "age": 30}
json_text = json.dumps(d) # '{"name": "Alice", "age": 30}'
data = json.loads(json_text) # OK
# If you received a Python-repr string, use ast.literal_eval (carefully)
import ast
data = ast.literal_eval("{'name': 'Alice', 'age': 30}")Cause 5: Trailing Commas
Trailing commas after the last element ([1, 2, 3,]) are valid in Python and JavaScript but not in JSON.
Broken:
text = '{"items": [1, 2, 3,], "count": 3,}'
data = json.loads(text) # JSONDecodeError: Expecting valueFixed — strip trailing commas (or use the json5 library):
import re
def strip_trailing_commas(text: str) -> str:
# Remove trailing commas before ] or }
return re.sub(r',\s*([}\]])', r'\1', text)
clean = strip_trailing_commas(text)
data = json.loads(clean)
# Or use the json5 library for lenient parsing
# pip install json5
import json5
data = json5.loads(text)Cause 6: API Error Message Not JSON (500 Error)
When a server throws an unhandled exception, it often returns an HTML error page. Your code gets a 500 response with HTML body and crashes when calling .json().
import requests
import json
import logging
def call_api(url: str) -> dict | None:
try:
response = requests.get(url, timeout=10)
except requests.RequestException as e:
logging.error("Network error: %s", e)
return None
if not response.ok:
logging.error(
"API error %s: %s",
response.status_code,
response.text[:300]
)
return None
try:
return response.json()
except json.JSONDecodeError as e:
logging.error(
"Invalid JSON (status=%s): %s | body: %s",
response.status_code, e, response.text[:300]
)
return NoneSafe json.loads() Pattern
A reusable utility function that handles all common failure modes:
import json
import logging
from typing import Any
def safe_json_loads(text: str | bytes, fallback: Any = None) -> Any:
"""Parse JSON safely — returns fallback on any error."""
if not text:
return fallback
if isinstance(text, bytes):
text = text.decode("utf-8", errors="replace")
text = text.lstrip('').strip()
if not text:
return fallback
try:
return json.loads(text)
except json.JSONDecodeError as e:
logging.warning("JSONDecodeError at pos %d: %s | snippet: %r", e.pos, e.msg, text[:200])
return fallbackSafe requests.json() Pattern
import requests
import json
import logging
def get_json(url: str, **kwargs) -> dict | None:
"""Fetch a URL and return parsed JSON or None."""
try:
r = requests.get(url, timeout=15, **kwargs)
except requests.RequestException as e:
logging.error("Request failed: %s", e)
return None
if not r.ok:
logging.error("HTTP %s from %s: %s", r.status_code, url, r.text[:300])
return None
ct = r.headers.get("Content-Type", "")
if "json" not in ct:
logging.warning("Unexpected Content-Type %r from %s", ct, url)
try:
return r.json()
except json.JSONDecodeError as e:
logging.error("JSONDecodeError from %s: %s | body: %r", url, e, r.text[:300])
return NoneValidate your JSON before parsing
Paste any JSON string and instantly see if it's valid — and where the error is.
Validate your JSON before parsing →Frequently Asked Questions
What is json.JSONDecodeError in Python?
It is a subclass of ValueError raised by json.loads() when the input is not valid JSON. It includes the error position via e.lineno ande.pos.
Why does requests.json() raise JSONDecodeError?
Usually because the server returned HTML (an error page) instead of JSON. Always checkresponse.status_code andresponse.headers["Content-Type"] before calling .json().
How do I handle JSONDecodeError safely?
Wrap json.loads() in a try/except json.JSONDecodeError block, log the raw text and error position, and return a safe fallback such as None or{}.
What causes "Expecting value: line 1 column 1"?
The input is either an empty string or starts with an invalid character such as< (HTML). Print repr(response.text[:100]) to see exactly what was received.
How do I fix BOM characters in JSON?
Strip the BOM with text.lstrip('\ufeff'), or open JSON files with encoding="utf-8-sig" which strips the BOM automatically.