Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get error location from json.loads in Python

Tags:

python

json

When I use json.loads in Python 3 and catch any resulting errors, like:

try:
  data = json.loads(string)
except ValueError as err:
  print(err)

I get a helpful message like:

Expecting ',' delimiter: line 12 column 12 (char 271)

I would like to be able to display this to the user, along with exactly the location which is causing the problem (I am reading in user-written JSON). How can I get out the line and column?

I could use a regex on err, but that feels like a bad idea, as I don't know if this message is internationalised, and could change in different versions of python. Is there a better way?

like image 593
Chris Jefferson Avatar asked Oct 22 '13 13:10

Chris Jefferson


4 Answers

If you use simplejson library, you get a well qualified JSONDecodeError:

class JSONDecodeError(ValueError):
   """Subclass of ValueError with the following additional properties:

   msg: The unformatted error message
   doc: The JSON document being parsed
   pos: The start index of doc where parsing failed
   end: The end index of doc where parsing failed (may be None)
   lineno: The line corresponding to pos
   colno: The column corresponding to pos
   endlineno: The line corresponding to end (may be None)
   endcolno: The column corresponding to end (may be None)

   """

Hopefully, this will be merged into stdlib soon.

like image 194
musically_ut Avatar answered Oct 27 '22 17:10

musically_ut


if json object is small, Past your Json object here http://jsonlint.com/ it gives where json breaks.

like image 22
MONTYHS Avatar answered Oct 27 '22 16:10

MONTYHS


[This answer is outdated. See other answers for modern python versions]

Scanning the json/decoder.py source code, we can see that the decoder's error messages are constructed using the errmsg function:

def errmsg(msg, doc, pos, end=None):
    # Note that this function is called from _json
    lineno, colno = linecol(doc, pos)
    if end is None:
        fmt = '{0}: line {1} column {2} (char {3})'
        return fmt.format(msg, lineno, colno, pos)
        #fmt = '%s: line %d column %d (char %d)'
        #return fmt % (msg, lineno, colno, pos)
    endlineno, endcolno = linecol(doc, end)
    fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
    return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
    #fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
    #return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)

Since this is a pure-python module, it's easy to wrap this function with a custom one. This process is known as monkey patching:

import json

original_errmsg= json.decoder.errmsg

def our_errmsg(msg, doc, pos, end=None):
    json.last_error_position= json.decoder.linecol(doc, pos)
    return original_errmsg(msg, doc, pos, end)

json.decoder.errmsg= our_errmsg

try:
    data = json.loads('{1:}')
except ValueError as e:
    print("error at", json.last_error_position)

Obviously, this solution is not ideal, since the implementation may change at any time, although it's still better than relying on the message. You should check if errmsg exists before patching (and possibly if there's no other arguments, or use varargs).

like image 9
loopbackbee Avatar answered Oct 27 '22 15:10

loopbackbee


In Python 3.5 and up, a specialized JSONDecodeError will be raised instead of ValueError. It has several useful attributes - quoting from the documentation:

msg: The unformatted error message.
doc: The JSON document being parsed.
pos: The start index of doc where parsing failed.
lineno: The line corresponding to pos.
colno: The column corresponding to pos.

like image 1
Karl Knechtel Avatar answered Oct 27 '22 15:10

Karl Knechtel