Handling invalid JSON

json · Practical · 8 min

Input from users, files and APIs is regularly malformed or not what you expected.

json.loads and json.load raise json.JSONDecodeError on malformed input. It's a subclass of ValueError with useful attributes:

  • e.msg the reason, e.g. "Expecting value" or "Illegal trailing comma before end of object" (3.13 wording)
  • e.lineno, e.colno and e.pos (character offset) for pointing at the problem
  • e.doc the whole input

Common causes: an empty string (often an empty HTTP body), a trailing comma, single quotes, comments, or HTML from an error page where JSON was expected. Check the HTTP status and content type before parsing API responses.

Parsing successfully doesn't mean the data is correct. json.loads("42") is valid JSON but not the object you wanted. After decoding, check the shape:

if not isinstance(data, dict) or "id" not in data:
    raise ValueError("expected an object with an id")

For anything larger, define the expected structure once with a validation library such as Pydantic or jsonschema, or convert the dict into a dataclass in one place, so the rest of your code can trust the types.

Encoding can fail too: TypeError for unsupported types, and ValueError for NaN when allow_nan=False or for circular references.

Example

import json

def parse(text):
    try:
        data = json.loads(text)
    except json.JSONDecodeError as e:
        return f"line {e.lineno} col {e.colno}: {e.msg}"
    if not isinstance(data, dict) or not isinstance(data.get("id"), int):
        return "valid JSON, wrong shape"
    return data

print(parse('{"id": 1}'))            # {'id': 1}
print(parse(''))                     # line 1 col 1: Expecting value
print(parse('{"id": 1,}'))
# line 1 col 9: Illegal trailing comma before end of object
print(parse("{'id': 1}"))
# line 1 col 2: Expecting property name enclosed in double quotes
print(parse('[1, 2]'))               # valid JSON, wrong shape
print(issubclass(json.JSONDecodeError, ValueError))  # True

Remember

  • Catch json.JSONDecodeError (a ValueError) and report lineno/colno
  • Empty strings and HTML error pages are the most common culprits
  • Validate the decoded structure, not just the syntax

Watch out for

  • Catching every exception around a whole request handler and hiding the decode position that would explain the bug.

Check yourself

  1. json.loads('') is called on an empty API response body. What happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/json.html#json.JSONDecodeError