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) 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) 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>, andjson.loads("<!DOCTYPE html>")fails with the exact samechar 0message. - Only whitespace.
json.loads(" \n")fails atline 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() 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'} 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()) 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}] 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 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",}' ^ 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
- Read the message: "Expecting value" vs "BOM" vs "property name" vs "Extra data" are different bugs.
- Look at the first 4 bytes of the file or response.
- For APIs, check the status code and
Content-Typebefore calling.json(). - Read files you didn't write with
encoding="utf-8-sig". - Write JSON with
json.dump(), neverstr(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.