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.

InheritsBaseException›Exception›ValueError›JSONDecodeError
json exceptionPython 3.5+Live demo
json.JSONDecodeError(msgmsg — The bare error message, e.g. "Expecting value". Stored in e.msg.type: str · required, docdoc — The whole document being parsed. Stored in e.doc.type: str · required, pospos — Index into doc where parsing failed. lineno and colno are computed from it.type: int · required)
Raised by
json.loads(s), json.load(f), JSONDecoder().decode(s) / raw_decode(s)
Message
Expecting value: line 1 column 1 (char 0)
Quick fix
except json.JSONDecodeError as e: report e.msg, e.lineno, e.colno
Watch out
pos counts characters of the str, not bytes of the file

Demo

Live evaluation
Parse your text without catching anything: the last line of the traceback is what you see.
Try:
Inputs
textstra JSON document
Code
import json
json.loads('[1, 2,]')
Uncaught exception
json.decoder.JSONDecodeError: Illegal trailing comma before end of array: line 1 column 6 (char 5)

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

NameTypeRequiredDescription
msgstryesThe bare error message, e.g. "Expecting value". Stored in e.msg.
docstryesThe whole document being parsed. Stored in e.doc.
posintyesIndex into doc where parsing failed. lineno and colno are computed from it.

Attributes

AttributeTypeMeaning
msgstrThe message without the position, e.g. "Expecting ',' delimiter".
docstrThe complete text that was being parsed (for load: everything read from the file).
posintIndex into doc where parsing failed, counting from 0 — characters, not bytes.
linenointLine of pos, counting from 1.
colnointColumn of pos within its line, counting from 1.
argstupleOne item: the full message with the position, the same as str(e).

Common patterns

Report the position
All the information is on the exception — no need to parse the message.
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}')
Show the offending line
Point at the column with a caret, like a compiler would.
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)
Translate to your own error
Keep the original as __cause__ with raise ... from.
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

1. Read the attributes
import json try: json.loads('{"a": }') except json.JSONDecodeError as e: info = (e.msg, e.lineno, e.colno, e.pos) info
Returns
('Expecting value', 1, 7, 6)
2. Traceback shows json.decoder
import json json.loads('[1, 2')
Returns
json.decoder.JSONDecodeError: Expecting ',' delimiter: line 1 column 6 (char 5)
3. Line and column across lines
import json text = '{\n "a": 1,\n "b": 2,\n}' try: json.loads(text) except json.JSONDecodeError as e: where = (e.lineno, e.colno, e.msg) where
Returns
(3, 9, 'Illegal trailing comma before end of object')
4. It is a ValueError
import json issubclass(json.JSONDecodeError, ValueError)
Returns
True
5. except ValueError catches it
import json try: json.loads('nope') except ValueError as e: caught = type(e).__name__ caught
Returns
'JSONDecodeError'
6. Two names, one class
import json json.JSONDecodeError is json.decoder.JSONDecodeError
Returns
True
7. msg, str() and args
import json e = json.JSONDecodeError('Bad value', '[1, x]', 4) (e.msg, str(e), e.args)
Returns
('Bad value', 'Bad value: line 1 column 5 (char 4)', ('Bad value: line 1 column 5 (char 4)',))
8. Raise it from your own parser
import json raise json.JSONDecodeError('Custom check failed', 'abc\ndef', 5)
Returns
json.decoder.JSONDecodeError: Custom check failed: line 2 column 2 (char 5)

Pitfalls

1. The bare name is not defined
import json does not put JSONDecodeError in your namespace. The except clause is only evaluated when an error happens — so the typo surfaces as a NameError at the worst moment.
except JSONDecodeError
import json
try:
    json.loads('{"a": }')
except JSONDecodeError:
    result = 'bad'
NameError: name 'JSONDecodeError' is not defined
json.JSONDecodeError
import json
try:
    json.loads('{"a": }')
except json.JSONDecodeError:
    result = 'bad'
result
'bad'
2. except ValueError hides unrelated bugs
Catching the parent class also catches every other ValueError in the block — here a valid document with a bad value is reported as invalid JSON.
except ValueError
import json
def parse(text):
    try:
        return int(json.loads(text)['count'])
    except ValueError:
        return 'invalid JSON'
parse('{"count": "ten"}')
'invalid JSON'
except json.JSONDecodeError
import json
def parse(text):
    try:
        return int(json.loads(text)['count'])
    except json.JSONDecodeError:
        return 'invalid JSON'
parse('{"count": "ten"}')
ValueError: invalid literal for int() with base 10: 'ten'
3. Using pos as a byte offset
pos indexes the decoded str. In the UTF-8 bytes every non-ASCII character before it takes more than one byte, so the same offset lands somewhere else.
bytes[pos:]
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
b' "age"'
str[pos:]
import json
data = '{"name": "Zoë" "age": 3}'
try:
    json.loads(data)
except json.JSONDecodeError as e:
    near = data[e.pos:e.pos + 6]
near
'"age":'

When to use

Use it
  • 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
Reach for something else
  • 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

CPython impl
Lib/json/decoder.py — class JSONDecodeError(ValueError); __init__ computes lineno and colno from doc and pos and passes the formatted message to ValueError
Module path
Defined in json.decoder and re-exported as json.JSONDecodeError; tracebacks show json.decoder.JSONDecodeError
Message format
"<msg>: line L column C (char P)" — P counts from 0, L and C from 1
Pickling
__reduce__ returns (msg, doc, pos), so it pickles and unpickles with all attributes

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.