ValueError: No JSON object could be decoded — causes and fixes
The Python 2 / requests error that means the response body was not JSON. How to see what actually came back, and the modern JSONDecodeError equivalent.
What causes "ValueError: No JSON object could be decoded"?
It means Python tried to parse something that was not JSON — most often an empty response body, an HTML error page, or plain text. The message is the Python 2 / older simplejson wording; Python 3 raises json.JSONDecodeError with a more specific message instead. The fix is always the same: print the raw text before parsing it.
Where the message comes from
You will see this exact wording from Python 2's json module, from simplejson, or from requests' response.json() when it wraps an older decoder. On Python 3 the equivalent failure reads Expecting value: line 1 column 1 (char 0).
# Python 2 / simplejson ValueError: No JSON object could be decoded # Python 3 — same root cause, clearer message json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Step 1 — look at what you actually received
Ninety percent of these are solved by one print statement. Never call .json() before you have seen the body at least once:
import requests
r = requests.get(url)
print(r.status_code) # 500? 404? 302?
print(repr(r.text[:300])) # '' or '<!DOCTYPE html>...' ?
print(r.headers.get("content-type"))
data = r.json() # only after the above looks rightThe four usual culprits
1. Empty body
A 204, a HEAD request, or a failed call can return zero bytes. json.loads("") always fails.
if not r.text.strip():
raise RuntimeError(f"Empty body (HTTP {r.status_code})")2. HTML instead of JSON
An error page, a login redirect, or a proxy notice. The body starts with < — see the dedicated guide on that error below.
ct = r.headers.get("content-type", "")
if "application/json" not in ct:
raise RuntimeError(f"Expected JSON, got {ct}: {r.text[:200]}")3. A Python dict repr, not JSON
Single quotes and True/None are Python syntax, not JSON. If the string came from str(some_dict), parse it with ast.literal_eval instead.
import ast, json
s = "{'name': 'Ada', 'active': True, 'note': None}"
json.loads(s) # fails — not JSON
ast.literal_eval(s) # {'name': 'Ada', 'active': True, 'note': None}
# Better: fix the producer to emit json.dumps(d)4. Reading a file that was already consumed
Calling f.read() twice, or json.load(f) after the cursor reached EOF, yields an empty string.
with open("data.json") as f:
text = f.read()
# data = json.load(f) # ✗ cursor is at EOF — empty!
data = json.loads(text) # ✓A safe wrapper
import json
def parse_json(text, source="input"):
if text is None or not text.strip():
raise ValueError(f"{source} was empty")
try:
return json.loads(text)
except ValueError as e: # catches JSONDecodeError too
raise ValueError(
f"{source} is not valid JSON: {e}\nFirst 200 chars: {text[:200]!r}"
) from eCatching ValueError works on both Python 2 and 3, because JSONDecodeError subclasses it.
Paste your JSON into the validator →
Get the exact line, column, and a fix hint in seconds — no upload, no signup.
Open JSON Validator →