json.JSONDecodeError
What json.loads and json.load raise for text that is not JSON. Catch it as json.JSONDecodeError — tracebacks call it json.decoder.JSONDecodeError, the module where it is defined — and read .lineno and .colno for the position.
Demo
import json json.loads('[1, 2,]')
The traceback names the class by the module that defines it, json.decoder; json.JSONDecodeError is the same class re-exported. In Raise, lineno is 1 plus the number of newlines before pos and colno is pos minus the index of the last newline before it — so pos 8, the newline itself, is still line 1, column 9. The constructor never checks pos: a negative value is used as a slice bound from the end, which gives line 2 with a negative column.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| msg | str | yes | The bare error message, e.g. "Expecting value". Stored in e.msg. |
| doc | str | yes | The whole document being parsed. Stored in e.doc. |
| pos | int | yes | Index into doc where parsing failed. lineno and colno are computed from it. |
Attributes
| Attribute | Type | Meaning |
|---|---|---|
| msg | str | The message without the position, e.g. "Expecting ',' delimiter". |
| doc | str | The complete text that was being parsed (for load: everything read from the file). |
| pos | int | Index into doc where parsing failed, counting from 0 — characters, not bytes. |
| lineno | int | Line of pos, counting from 1. |
| colno | int | Column of pos within its line, counting from 1. |
| args | tuple | One item: the full message with the position, the same as str(e). |
Common patterns
import json try: data = json.loads(text) except json.JSONDecodeError as e: print(f'invalid JSON at line {e.lineno}, column {e.colno}: {e.msg}')
import json try: json.loads(text) except json.JSONDecodeError as e: line = e.doc.splitlines()[e.lineno - 1] if e.doc else '' print(line) print(' ' * (e.colno - 1) + '^', e.msg)
import json class BadPayload(Exception): pass def parse(body): try: return json.loads(body) except json.JSONDecodeError as e: raise BadPayload(f'body is not JSON ({e.msg})') from e
Examples
Pitfalls
import json try: json.loads('{"a": }') except JSONDecodeError: result = 'bad'
import json try: json.loads('{"a": }') except json.JSONDecodeError: result = 'bad' result
import json def parse(text): try: return int(json.loads(text)['count']) except ValueError: return 'invalid JSON' parse('{"count": "ten"}')
import json def parse(text): try: return int(json.loads(text)['count']) except json.JSONDecodeError: return 'invalid JSON' parse('{"count": "ten"}')
import json data = '{"name": "Zoë" "age": 3}' try: json.loads(data) except json.JSONDecodeError as e: near = data.encode('utf-8')[e.pos:e.pos + 6] near
import json data = '{"name": "Zoë" "age": 3}' try: json.loads(data) except json.JSONDecodeError as e: near = data[e.pos:e.pos + 6] near
When to use
- Catch it around json.loads / json.load when the input comes from outside
- Raise it from your own JSON-like parser so callers can handle both the same way
- Use lineno/colno to point users at the broken spot in a config file
- Catching ValueError when you mean only bad JSON — it hides other errors
- Parsing str(e) for the position — use the attributes
- Expecting it for wrong input types: json.loads(None) raises TypeError
Notes
FAQ
The parser found no JSON value at the very start. Usually the text is empty (an empty HTTP response or file), is an HTML error page, or you passed a file name to json.loads instead of the content.