Expecting value: line 1 column 1 (char 0)

Python reached a position where a value has to start and found something that cannot start one. The offset matters: char 0 means the document was empty or never JSON at all, while a larger offset points at one bad value inside an otherwise fine document.

How the message appears

CPython's json module reports line, column and character offset. The offset counts characters from the start of the document, which is the one to trust when the whole document is a single line.

  • json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
  • json.decoder.JSONDecodeError: Expecting value: line 3 column 13 (char 32)
  • requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

What causes it

The body was empty (this is what char 0 usually means)

A 204 No Content, a HEAD request, a redirect that returned nothing, or a connection that closed early. response.json() calls json.loads(""), and an empty string has no value in it. Print repr(response.text[:200]) and the cause is normally obvious immediately.

It is an HTML page, not JSON

Position 0 is <. A 404 page, a login redirect, a proxy error or a WAF challenge — all of them return HTML with a perfectly cheerful 200 in some setups. Calling response.raise_for_status() first converts most of these into an error that names the real problem.

Python literals in the document

JSON has null, true and false. Python writes None, True and False, and a document built with str(some_dict) or copied from a repr will contain them. That is the case the sample below shows.

You passed a path instead of the contents

json.loads("data.json") tries to parse the eight characters of the filename. Use json.load(f) with an open file, or json.loads(path.read_text()).

The fix

Replace the Python literals with JSON ones — None becomes null, True becomes true, False becomes false. If the reported offset is char 0, stop looking at the JSON and look at what the server actually returned.

Fails to parse
{
  "name": "Ada",
  "active": None
}
Parses
{
  "name": "Ada",
  "active": null
}

The example travels in the URL fragment, which browsers never send to a server.

Worth knowing

  • Never build JSON with str(obj) or an f-string. json.dumps(obj) is the only thing that produces valid JSON from a Python object, and it is also what escapes quotes correctly.
  • Python is more permissive than JSON in one direction that surprises people: json.loads accepts NaN, Infinity and -Infinity by default, and json.dumps emits them. Pass allow_nan=False when producing documents other parsers have to read.
  • The exception carries the offsets: except json.JSONDecodeError as e: gives you e.lineno, e.colno, e.pos and e.doc, which is far more useful in a log than the string form.

Your JSON and text are processed entirely in your browser and are never sent anywhere. To understand how the site is used we record each page view — the page, the referrer and an approximate location from your IP address — and keep it for 90 days. Nothing is stored on your device unless you choose it. Accept to also store a device identifier here and record your browser and device characteristics, which is what lets a return visit be told from a new one. Decline and nothing is sent at all. We respect Do Not Track. See our privacy policy.