Fix: Python json.decoder.JSONDecodeError: Expecting value
Quick Answer
How to fix Python JSONDecodeError Expecting value caused by empty responses, HTML error pages, invalid JSON, BOM characters, and API errors.
The Error
You parse JSON in Python and get:
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)Or variations:
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 100)json.decoder.JSONDecodeError: Unterminated string starting at: line 5 column 12 (char 89)json.decoder.JSONDecodeError: Expecting ',' delimiter: line 3 column 15 (char 45)Python’s json.loads() or json.load() cannot parse the input as valid JSON. The input is either empty, not JSON at all, or contains syntax errors.
Why This Happens
json.loads() expects a valid JSON string. When it encounters something that is not valid JSON, it raises JSONDecodeError with a message indicating where the parsing failed.
Common causes:
- Empty string or response.
json.loads("")fails immediately with “Expecting value.” - HTML error page instead of JSON. The server returned an HTML 404 or 500 page.
- Single quotes instead of double quotes. JSON requires double quotes:
{"key": "value"}, not{'key': 'value'}. - Trailing commas.
{"a": 1, "b": 2,}is invalid JSON. - Comments in JSON. JSON does not support comments (
//or/* */). - BOM (Byte Order Mark). A UTF-8 BOM at the start of the file.
- Multiple JSON objects. Multiple JSON objects concatenated without being in an array.
- Unquoted keys.
{key: "value"}is not valid JSON.
Fix 1: Check the Response Before Parsing
The most common cause is parsing an empty or non-JSON response:
Broken:
import requests
import json
response = requests.get("https://api.example.com/data")
data = response.json() # JSONDecodeError if response body is empty or not JSON!Fixed — check status code and content first:
response = requests.get("https://api.example.com/data")
# Check if the request succeeded
if response.status_code != 200:
print(f"Error: HTTP {response.status_code}")
print(f"Response body: {response.text[:500]}") # Print first 500 chars for debugging
raise Exception(f"API returned {response.status_code}")
# Check content type
content_type = response.headers.get("Content-Type", "")
if "application/json" not in content_type:
print(f"Expected JSON but got: {content_type}")
print(f"Response: {response.text[:500]}")
raise Exception(f"Non-JSON response: {content_type}")
# Check if body is not empty
if not response.text.strip():
print("Empty response body")
raise Exception("Empty response")
data = response.json()Simplified with try-except:
try:
data = response.json()
except json.JSONDecodeError as e:
print(f"Failed to parse JSON: {e}")
print(f"Response status: {response.status_code}")
print(f"Response body: {response.text[:500]}")
raisePro Tip: Always check
response.status_codebefore calling.json(). A 500 error page is usually HTML, not JSON. Printresponse.text[:500]in your error handler to see what the server actually returned — it is almost always immediately obvious (HTML page, empty string, plain text error).
Fix 2: Fix the JSON Syntax
Common JSON syntax errors:
Single quotes (not valid JSON):
# Broken — single quotes
bad = "{'name': 'Alice', 'age': 30}"
json.loads(bad) # JSONDecodeError!
# Fixed — double quotes
good = '{"name": "Alice", "age": 30}'
json.loads(good)
# If you have Python dict-like strings, use ast.literal_eval instead
import ast
data = ast.literal_eval("{'name': 'Alice', 'age': 30}")Trailing commas:
# Broken — trailing comma after last element
bad = '{"a": 1, "b": 2,}'
json.loads(bad) # JSONDecodeError!
# Fixed — remove trailing comma
good = '{"a": 1, "b": 2}'Comments:
# Broken — JSON doesn't support comments
bad = '''
{
// This is a comment
"name": "Alice",
/* This too */
"age": 30
}
'''
# Fixed — remove comments before parsing
import re
cleaned = re.sub(r'//.*?$|/\*.*?\*/', '', bad, flags=re.MULTILINE | re.DOTALL)
json.loads(cleaned)
# Or use a library that supports JSON with comments
# pip install json5
import json5
data = json5.loads(bad)Unquoted keys:
# Broken — keys must be quoted in JSON
bad = '{name: "Alice"}'
# Fixed
good = '{"name": "Alice"}'Common Mistake: Assuming Python dict syntax and JSON are the same. They are not. JSON requires double quotes for strings, no trailing commas, no comments, no single quotes, and no Python literals like
True/False/None(JSON usestrue/false/null).
Fix 3: Fix BOM and Encoding Issues
A UTF-8 BOM (Byte Order Mark) at the start of a file causes “Expecting value”:
# The file starts with \xef\xbb\xbf (UTF-8 BOM)
with open("data.json", "r") as f:
data = json.load(f) # JSONDecodeError!Fixed — use utf-8-sig encoding:
with open("data.json", "r", encoding="utf-8-sig") as f:
data = json.load(f) # utf-8-sig strips the BOM automaticallyFixed — strip BOM manually:
with open("data.json", "rb") as f:
content = f.read()
if content.startswith(b'\xef\xbb\xbf'):
content = content[3:] # Remove BOM
data = json.loads(content.decode("utf-8"))Check for encoding issues:
with open("data.json", "rb") as f:
raw = f.read(10)
print(raw) # See the actual bytes
# b'\xef\xbb\xbf{...' means BOM is presentFix 4: Fix File Reading Issues
Reading a file incorrectly:
Broken — file path wrong or file is empty:
with open("config.json", "r") as f:
data = json.load(f) # JSONDecodeError if file is empty!Fixed — check file contents:
import os
import json
filepath = "config.json"
if not os.path.exists(filepath):
raise FileNotFoundError(f"{filepath} does not exist")
if os.path.getsize(filepath) == 0:
raise ValueError(f"{filepath} is empty")
with open(filepath, "r", encoding="utf-8") as f:
data = json.load(f)Broken — reading the file twice (cursor at end):
with open("data.json", "r") as f:
print(f.read()) # Read the entire file
data = json.load(f) # JSONDecodeError! File cursor is at the end
# Fixed — seek back to start
with open("data.json", "r") as f:
print(f.read())
f.seek(0) # Reset cursor to beginning
data = json.load(f)Fix 5: Fix Multiple JSON Objects (JSONL / NDJSON)
A file with one JSON object per line is not valid JSON as a whole:
{"id": 1, "name": "Alice"}
{"id": 2, "name": "Bob"}
{"id": 3, "name": "Charlie"}Broken:
with open("data.jsonl", "r") as f:
data = json.load(f) # JSONDecodeError: Extra dataFixed — parse line by line:
records = []
with open("data.jsonl", "r") as f:
for line in f:
line = line.strip()
if line:
records.append(json.loads(line))Fixed — use a list comprehension:
with open("data.jsonl", "r") as f:
records = [json.loads(line) for line in f if line.strip()]Fix 6: Fix API-Specific Issues
Empty responses on 204 No Content:
response = requests.delete(f"/api/items/{item_id}")
if response.status_code == 204:
# 204 means success with no body — don't parse JSON
return None
data = response.json()Paginated APIs returning empty on last page:
def fetch_all_pages(base_url):
all_items = []
page = 1
while True:
response = requests.get(f"{base_url}?page={page}")
if response.status_code != 200:
break
try:
data = response.json()
except json.JSONDecodeError:
break
if not data.get("items"):
break
all_items.extend(data["items"])
page += 1
return all_itemsRate-limited APIs returning HTML:
response = requests.get("https://api.example.com/data")
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)
return fetch_data() # RetryFix 7: Fix Python Literals vs JSON
Python literals and JSON look similar but are different:
# Python dict (not JSON!)
python_str = "{'key': True, 'value': None}"
json.loads(python_str) # Fails! True/None are not valid JSON
# Convert Python literals to JSON
import ast
python_obj = ast.literal_eval(python_str)
json_str = json.dumps(python_obj)
# Now json_str is: '{"key": true, "value": null}'Boolean and null differences:
| Python | JSON |
|---|---|
True | true |
False | false |
None | null |
'single' | "double" only |
Fix 8: Use a Robust JSON Parser
For JSON that is slightly malformed:
# pip install json5
import json5
# Handles comments, trailing commas, single quotes, unquoted keys
data = json5.loads("""
{
// Configuration file
name: 'My App',
debug: true,
ports: [8080, 8443,], // trailing comma OK
}
""")For very large JSON files, use streaming parsers:
# pip install ijson
import ijson
with open("huge.json", "rb") as f:
for item in ijson.items(f, "items.item"):
process(item)Still Not Working?
Print the raw content to see what you are actually parsing:
print(repr(content)) # repr shows invisible characters like \r, \n, \xef
print(len(content)) # Check if it's empty
print(content[:100]) # Print first 100 charactersCheck for compressed responses. Some APIs return gzip-compressed data:
response = requests.get(url, headers={"Accept-Encoding": "gzip"})
# requests automatically decompresses, but manual HTTP calls might notCheck for JSONP responses. Some older APIs wrap JSON in a callback function:
# JSONP: callback({"data": "value"})
text = response.text
if text.startswith("callback("):
text = text[len("callback("):-1] # Strip the wrapper
data = json.loads(text)For Python type errors with None values, see Fix: Python TypeError: ‘NoneType’ object is not subscriptable. For file not found errors, see Fix: Python FileNotFoundError. For requests connection errors, see Fix: Python requests ConnectionError: Max retries exceeded.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS Lambda Unable to import module / Runtime.ImportModuleError
How to fix the AWS Lambda Runtime.ImportModuleError and Unable to import module error caused by wrong handler paths, missing dependencies, layer issues, and packaging problems.
Fix: Python TypeError: unhashable type: 'list'
Learn why Python raises TypeError unhashable type list, dict, or set and how to fix it when using dictionary keys, sets, groupby, dataclasses, and custom classes.
Fix: Django Forbidden (403) CSRF verification failed
How to fix Django 403 CSRF verification failed error caused by missing CSRF tokens, AJAX requests, cross-origin issues, HTTPS misconfig, and session problems.
Fix: FastAPI 422 Unprocessable Entity (validation error)
How to fix FastAPI 422 Unprocessable Entity error caused by wrong request body format, missing fields, type mismatches, query parameter errors, and Pydantic validation.