diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2015-01-26 13:16:30 +0200 |
---|---|---|
committer | Serhiy Storchaka <storchaka@gmail.com> | 2015-01-26 13:16:30 +0200 |
commit | 47efb4a5dc9cdf3f59362beb9a04c5c6bdd09f81 (patch) | |
tree | b3e5115125360618a02a9615a643cbebe4aa7e75 /Lib/json | |
parent | 4e5d9eac2ca03debecbb9c6ed873303d04ead06a (diff) | |
download | cpython-git-47efb4a5dc9cdf3f59362beb9a04c5c6bdd09f81.tar.gz |
Issue #19361: JSON decoder now raises JSONDecodeError instead of ValueError.
Diffstat (limited to 'Lib/json')
-rw-r--r-- | Lib/json/__init__.py | 7 | ||||
-rw-r--r-- | Lib/json/decoder.py | 86 |
2 files changed, 45 insertions, 48 deletions
diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py index 94f7d8c0fa..6ce9880b26 100644 --- a/Lib/json/__init__.py +++ b/Lib/json/__init__.py @@ -98,12 +98,12 @@ Using json.tool from the shell to validate and pretty-print:: __version__ = '2.0.9' __all__ = [ 'dump', 'dumps', 'load', 'loads', - 'JSONDecoder', 'JSONEncoder', + 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', ] __author__ = 'Bob Ippolito <bob@redivi.com>' -from .decoder import JSONDecoder +from .decoder import JSONDecoder, JSONDecodeError from .encoder import JSONEncoder _default_encoder = JSONEncoder( @@ -311,7 +311,8 @@ def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, raise TypeError('the JSON object must be str, not {!r}'.format( s.__class__.__name__)) if s.startswith(u'\ufeff'): - raise ValueError("Unexpected UTF-8 BOM (decode using utf-8-sig)") + raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)", + s, 0) if (cls is None and object_hook is None and parse_int is None and parse_float is None and parse_constant is None and object_pairs_hook is None and not kw): diff --git a/Lib/json/decoder.py b/Lib/json/decoder.py index 59e5f41f4d..0f03f20042 100644 --- a/Lib/json/decoder.py +++ b/Lib/json/decoder.py @@ -8,7 +8,7 @@ try: except ImportError: c_scanstring = None -__all__ = ['JSONDecoder'] +__all__ = ['JSONDecoder', 'JSONDecodeError'] FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL @@ -17,32 +17,30 @@ PosInf = float('inf') NegInf = float('-inf') -def linecol(doc, pos): - if isinstance(doc, bytes): - newline = b'\n' - else: - newline = '\n' - lineno = doc.count(newline, 0, pos) + 1 - if lineno == 1: - colno = pos + 1 - else: - colno = pos - doc.rindex(newline, 0, pos) - return lineno, colno - - -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) +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 + lineno: The line corresponding to pos + colno: The column corresponding to pos + + """ + # Note that this exception is used from _json + def __init__(self, msg, doc, pos): + lineno = doc.count('\n', 0, pos) + 1 + colno = pos - doc.rfind('\n', 0, pos) + errmsg = '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos) + ValueError.__init__(self, errmsg) + self.msg = msg + self.doc = doc + self.pos = pos + self.lineno = lineno + self.colno = colno + + def __reduce__(self): + return self.__class__, (self.msg, self.doc, self.pos) _CONSTANTS = { @@ -66,7 +64,7 @@ def _decode_uXXXX(s, pos): except ValueError: pass msg = "Invalid \\uXXXX escape" - raise ValueError(errmsg(msg, s, pos)) + raise JSONDecodeError(msg, s, pos) def py_scanstring(s, end, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match): @@ -84,8 +82,7 @@ def py_scanstring(s, end, strict=True, while 1: chunk = _m(s, end) if chunk is None: - raise ValueError( - errmsg("Unterminated string starting at", s, begin)) + raise JSONDecodeError("Unterminated string starting at", s, begin) end = chunk.end() content, terminator = chunk.groups() # Content is contains zero or more unescaped string characters @@ -99,22 +96,21 @@ def py_scanstring(s, end, strict=True, if strict: #msg = "Invalid control character %r at" % (terminator,) msg = "Invalid control character {0!r} at".format(terminator) - raise ValueError(errmsg(msg, s, end)) + raise JSONDecodeError(msg, s, end) else: _append(terminator) continue try: esc = s[end] except IndexError: - raise ValueError( - errmsg("Unterminated string starting at", s, begin)) + raise JSONDecodeError("Unterminated string starting at", s, begin) # If not a unicode escape sequence, must be in the lookup table if esc != 'u': try: char = _b[esc] except KeyError: msg = "Invalid \\escape: {0!r}".format(esc) - raise ValueError(errmsg(msg, s, end)) + raise JSONDecodeError(msg, s, end) end += 1 else: uni = _decode_uXXXX(s, end) @@ -163,8 +159,8 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, pairs = object_hook(pairs) return pairs, end + 1 elif nextchar != '"': - raise ValueError(errmsg( - "Expecting property name enclosed in double quotes", s, end)) + raise JSONDecodeError( + "Expecting property name enclosed in double quotes", s, end) end += 1 while True: key, end = scanstring(s, end, strict) @@ -174,7 +170,7 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, if s[end:end + 1] != ':': end = _w(s, end).end() if s[end:end + 1] != ':': - raise ValueError(errmsg("Expecting ':' delimiter", s, end)) + raise JSONDecodeError("Expecting ':' delimiter", s, end) end += 1 try: @@ -188,7 +184,7 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, try: value, end = scan_once(s, end) except StopIteration as err: - raise ValueError(errmsg("Expecting value", s, err.value)) from None + raise JSONDecodeError("Expecting value", s, err.value) from None pairs_append((key, value)) try: nextchar = s[end] @@ -202,13 +198,13 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, if nextchar == '}': break elif nextchar != ',': - raise ValueError(errmsg("Expecting ',' delimiter", s, end - 1)) + raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) end = _w(s, end).end() nextchar = s[end:end + 1] end += 1 if nextchar != '"': - raise ValueError(errmsg( - "Expecting property name enclosed in double quotes", s, end - 1)) + raise JSONDecodeError( + "Expecting property name enclosed in double quotes", s, end - 1) if object_pairs_hook is not None: result = object_pairs_hook(pairs) return result, end @@ -232,7 +228,7 @@ def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): try: value, end = scan_once(s, end) except StopIteration as err: - raise ValueError(errmsg("Expecting value", s, err.value)) from None + raise JSONDecodeError("Expecting value", s, err.value) from None _append(value) nextchar = s[end:end + 1] if nextchar in _ws: @@ -242,7 +238,7 @@ def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): if nextchar == ']': break elif nextchar != ',': - raise ValueError(errmsg("Expecting ',' delimiter", s, end - 1)) + raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) try: if s[end] in _ws: end += 1 @@ -343,7 +339,7 @@ class JSONDecoder(object): obj, end = self.raw_decode(s, idx=_w(s, 0).end()) end = _w(s, end).end() if end != len(s): - raise ValueError(errmsg("Extra data", s, end, len(s))) + raise JSONDecodeError("Extra data", s, end) return obj def raw_decode(self, s, idx=0): @@ -358,5 +354,5 @@ class JSONDecoder(object): try: obj, end = self.scan_once(s, idx) except StopIteration as err: - raise ValueError(errmsg("Expecting value", s, err.value)) from None + raise JSONDecodeError("Expecting value", s, err.value) from None return obj, end |