JSONDecodeError, what it means and how to fix it

This error means json.loads (or a library call hiding it, like response.json()) received text that is not valid JSON, and the decoder failed at the stated line and column. The near-universal cause is that the text is not what the code assumed: an API returned an HTML error page or an empty body, or the data was a Python representation (single quotes, True, None) rather than real JSON.

What it looks like

import json
data = json.loads(response.text)   # the API actually sent an
                                   # HTML error page

Traceback (most recent call last):
  File "main.py", line 1, in <module>
json.decoder.JSONDecodeError: Expecting value:
line 1 column 1 (char 0)

# fixed: look at what actually arrived before parsing it
print(response.status_code, response.text[:200])

The fix, in order

  1. Print the raw text before parsing it. response.text[:200] (or the file's first bytes) settles everything: an HTML page, an empty string, a login redirect, or JSON with a visible defect. Never debug JSON blind.
  2. Check the status code and content type. APIs return HTML for 404s, 500s, and auth failures. A .json() call on a failed response produces this error, which sends debugging toward the parser when the real problem is the request. Branch on status first.
  3. Distinguish real JSON from a Python dict's repr. Single-quoted strings, True/False/None, and trailing commas are Python syntax, not JSON. Data produced by print(dict) or str(dict) needs json.dumps at the source, or ast.literal_eval to recover, never json.loads.
  4. Read char position for genuine JSON defects. “Expecting property name” points at a trailing comma or single quotes; “Unterminated string” at a truncated write. The position counts characters, so paste the text into a JSON validator to see it with context.
  5. For files, check for truncation and double-writes. A file written by two processes, or read while another program was mid-write, parses until the seam and fails there; write to a temp name and rename atomically.

If it still happens

If the text prints as valid JSON but still fails, suspect invisible contamination: a byte-order mark at the start (decode with utf-8-sig) or log lines and warnings mixed into the stream before the JSON begins (common when a script's output is piped). The decoder fails at char 0 precisely because the JSON does not start at char 0.

Common questions

Is JSONDecodeError a ValueError? Yes; it subclasses ValueError, which is why older code and documentation describe the same failure as a ValueError. Catching JSONDecodeError specifically is clearer, and catching ValueError catches both it and plain value errors.

The API worked yesterday and returns this today. What changed? Usually the response changed, not the JSON rules: an expired key turning success into an HTML 401 page is the classic. Step one (print the raw response and status) reveals it in seconds.

Related errors

UnicodeDecodeError · KeyError · ValueError: invalid literal for int()