DEV Community

Blogs World
Blogs World

Posted on

JSONDecodeError: Expecting value at char 0? Read the Error Position Before You Touch the JSON

json.load() fails, the message says Expecting value: line 1 column 1 (char 0), and the file looks fine in your editor. So you paste it into a validator, it passes, and you're stuck.

The fix is to stop guessing. A JSONDecodeError tells you exactly which character the parser choked on (e.pos), and the message itself names the cause. Here are six inputs that break json.load(), the exact error each one produces on Python 3.13, and a 12-line helper that shows you the bad spot.

Six broken inputs, six different messages

import json, codecs, pathlib

p = pathlib.Path
p("empty.json").write_text("")
p("bom.json").write_bytes(codecs.BOM_UTF8 + b'{"name": "Asha"}')
p("single.json").write_text("{'name': 'Asha'}")
p("trailing.json").write_text('{"name": "Asha", "city": "Udaipur",}')
p("jsonl.json").write_text('{"id": 1}\n{"id": 2}\n')

for f in ["empty.json", "bom.json", "single.json", "trailing.json", "jsonl.json"]:
    try:
        with open(f, encoding="utf-8") as fh:
            json.load(fh)
    except json.JSONDecodeError as e:
        print(f, "->", e)
Enter fullscreen mode Exit fullscreen mode

Output on Python 3.13.5:

empty.json -> Expecting value: line 1 column 1 (char 0)
bom.json -> Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)
single.json -> Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
trailing.json -> Illegal trailing comma before end of object: line 1 column 35 (char 34)
jsonl.json -> Extra data: line 2 column 1 (char 10)
Enter fullscreen mode Exit fullscreen mode

Read the message, not just the char number. Each one points to a different fix.

1. Expecting value ... (char 0): there is no JSON at all

char 0 means the parser found nothing it could start with. The usual causes:

  • The file is empty. Something truncated it, or you opened it with "w" before reading.
  • It's an HTML page. This is the big one with APIs. requests.get(url).json() on a 404, a login redirect, or a Cloudflare page gives you <!DOCTYPE html>, and json.loads("<!DOCTYPE html>") fails with the exact same char 0 message.
  • Only whitespace. json.loads(" \n") fails at line 2 column 1 (char 4), which is the end of the string.

For API calls, check the response before parsing:

r = requests.get(url, timeout=10)
r.raise_for_status()
if "application/json" not in r.headers.get("Content-Type", ""):
    raise ValueError(f"Expected JSON, got {r.headers.get('Content-Type')}: {r.text[:200]!r}")
data = r.json()
Enter fullscreen mode Exit fullscreen mode

2. Unexpected UTF-8 BOM: the file was saved by Windows tooling

Notepad, Excel exports and some PowerShell commands put three invisible bytes (EF BB BF) at the start. Python even tells you the fix in the message:

with open("bom.json", encoding="utf-8-sig") as fh:
    data = json.load(fh)   # {'name': 'Asha'}
Enter fullscreen mode Exit fullscreen mode

utf-8-sig strips the BOM if it's there and behaves like plain UTF-8 if it isn't, so it's safe to use everywhere you read files you didn't write yourself.

A related trap: PowerShell 5's > redirect writes UTF-16. Opening that with encoding="utf-8" doesn't even reach the JSON parser. It dies with UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0. Reading the file as bytes and passing them to json.loads() works, because json auto-detects UTF-8, UTF-16 and UTF-32 from raw bytes:

data = json.loads(open("u16.json", "rb").read())
Enter fullscreen mode Exit fullscreen mode

3. Expecting property name enclosed in double quotes: it's a Python dict, not JSON

{'name': 'Asha'} is what print(my_dict) or str(my_dict) produces. JSON needs double quotes. Fix the writer, not the reader: use json.dump(obj, f) instead of f.write(str(obj)). If you're stuck with old files that were written that way, ast.literal_eval() can read them safely. Don't use eval().

4. Illegal trailing comma: hand-edited config

JSON doesn't allow a comma after the last item. Older Python versions report this as Expecting property name enclosed in double quotes, which is confusing. Python 3.13 names it directly. The position (char 34) lands right on the stray comma.

5. Extra data: it's JSON Lines, not one JSON document

The first object parsed fine, and then the parser found more at line 2. That's a .jsonl / NDJSON file (one object per line), common in logs and data exports. Parse it line by line:

with open("jsonl.json", encoding="utf-8") as fh:
    rows = [json.loads(line) for line in fh if line.strip()]
# [{'id': 1}, {'id': 2}]
Enter fullscreen mode Exit fullscreen mode

6. The one that doesn't fail: NaN

json.loads('{"score": NaN}') happily returns {'score': nan}. Python accepts it, but strict parsers (JavaScript's JSON.parse, most APIs) reject it. If you produce JSON for someone else, use json.dumps(obj, allow_nan=False). It raises ValueError: Out of range float values are not JSON compliant: nan instead of quietly shipping invalid JSON.

A 12-line helper that shows the bad character

When the file is big, line and column numbers aren't enough. Print the bytes around e.pos:

import json

def load_json_debug(path):
    raw = open(path, "rb").read()
    print(f"{len(raw)} bytes, starts with {raw[:4]!r}")
    text = raw.decode("utf-8-sig")
    try:
        return json.loads(text)
    except json.JSONDecodeError as e:
        lo = max(e.pos - 30, 0)
        print(f"{e.msg} at line {e.lineno}, col {e.colno}")
        print(repr(text[lo:e.pos + 30]))
        print(" " * (e.pos - lo + 1) + "^")
        raise
Enter fullscreen mode Exit fullscreen mode

On the trailing-comma file it prints:

36 bytes, starts with b'{"na'
Illegal trailing comma before end of object at line 1, col 35
'me": "Asha", "city": "Udaipur",}'
                               ^
Enter fullscreen mode Exit fullscreen mode

The first line alone catches most cases. 0 bytes means an empty file, b'\xef\xbb\xbf{' means a BOM, b'\xff\xfe{\x00' means UTF-16, and b'<!DO' means HTML.

Quick checklist

  1. Read the message: "Expecting value" vs "BOM" vs "property name" vs "Extra data" are different bugs.
  2. Look at the first 4 bytes of the file or response.
  3. For APIs, check the status code and Content-Type before calling .json().
  4. Read files you didn't write with encoding="utf-8-sig".
  5. Write JSON with json.dump(), never str(dict).

If you want to see where a large document breaks before writing code, paste it into a JSON formatter and validator. It points at the failing line. For the basics of json.load vs json.loads, nested data and writing files back, there's a longer guide on reading JSON files in Python, and a roundup of other common Python errors and how to fix them.

I write practical debugging guides at Blogs World.

Top comments (0)