codekingpro/portable-devtools
114k
1"""Implementation of JSONDecoder2"""3import re4 5from json import scanner6try:7 from _json import scanstring as c_scanstring8except ImportError:9 c_scanstring = None10 11__all__ = ['JSONDecoder', 'JSONDecodeError']12 13FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL14 15NaN = float('nan')16PosInf = float('inf')17NegInf = float('-inf')18 19 20class JSONDecodeError(ValueError):21 """Subclass of ValueError with the following additional properties:22 23 msg: The unformatted error message24 doc: The JSON document being parsed25 pos: The start index of doc where parsing failed26 lineno: The line corresponding to pos27 colno: The column corresponding to pos28 29 """30 # Note that this exception is used from _json31 def __init__(self, msg, doc, pos):32 lineno = doc.count('\n', 0, pos) + 133 colno = pos - doc.rfind('\n', 0, pos)34 errmsg = '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos)35 ValueError.__init__(self, errmsg)36 self.msg = msg37 self.doc = doc38 self.pos = pos39 self.lineno = lineno40 self.colno = colno41 42 def __reduce__(self):43 return self.__class__, (self.msg, self.doc, self.pos)44 45 46_CONSTANTS = {47 '-Infinity': NegInf,48 'Infinity': PosInf,49 'NaN': NaN,50}51 52 53HEXDIGITS = re.compile(r'[0-9A-Fa-f]{4}', FLAGS)54STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)55BACKSLASH = {56 '"': '"', '\\': '\\', '/': '/',57 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t',58}59 60def _decode_uXXXX(s, pos, _m=HEXDIGITS.match):61 esc = _m(s, pos + 1)62 if esc is not None:63 try:64 return int(esc.group(), 16)65 except ValueError:66 pass67 msg = "Invalid \\uXXXX escape"68 raise JSONDecodeError(msg, s, pos)69 70def py_scanstring(s, end, strict=True,71 _b=BACKSLASH, _m=STRINGCHUNK.match):72 """Scan the string s for a JSON string. End is the index of the73 character in s after the quote that started the JSON string.74 Unescapes all valid JSON string escape sequences and raises ValueError75 on attempt to decode an invalid string. If strict is False then literal76 control characters are allowed in the string.77 78 Returns a tuple of the decoded string and the index of the character in s79 after the end quote."""80 chunks = []81 _append = chunks.append82 begin = end - 183 while 1:84 chunk = _m(s, end)85 if chunk is None:86 raise JSONDecodeError("Unterminated string starting at", s, begin)87 end = chunk.end()88 content, terminator = chunk.groups()89 # Content is contains zero or more unescaped string characters90 if content:91 _append(content)92 # Terminator is the end of string, a literal control character,93 # or a backslash denoting that an escape sequence follows94 if terminator == '"':95 break96 elif terminator != '\\':97 if strict:98 #msg = "Invalid control character %r at" % (terminator,)99 msg = "Invalid control character {0!r} at".format(terminator)100 raise JSONDecodeError(msg, s, end)101 else:102 _append(terminator)103 continue104 try:105 esc = s[end]106 except IndexError:107 raise JSONDecodeError("Unterminated string starting at",108 s, begin) from None109 # If not a unicode escape sequence, must be in the lookup table110 if esc != 'u':111 try:112 char = _b[esc]113 except KeyError:114 msg = "Invalid \\escape: {0!r}".format(esc)115 raise JSONDecodeError(msg, s, end)116 end += 1117 else:118 uni = _decode_uXXXX(s, end)119 end += 5120 if 0xd800 <= uni <= 0xdbff and s[end:end + 2] == '\\u':121 uni2 = _decode_uXXXX(s, end + 1)122 if 0xdc00 <= uni2 <= 0xdfff:123 uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))124 end += 6125 char = chr(uni)126 _append(char)127 return ''.join(chunks), end128 129 130# Use speedup if available131scanstring = c_scanstring or py_scanstring132 133WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)134WHITESPACE_STR = ' \t\n\r'135 136 137def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,138 memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR):139 s, end = s_and_end140 pairs = []141 pairs_append = pairs.append142 # Backwards compatibility143 if memo is None:144 memo = {}145 memo_get = memo.setdefault146 # Use a slice to prevent IndexError from being raised, the following147 # check will raise a more specific ValueError if the string is empty148 nextchar = s[end:end + 1]149 # Normally we expect nextchar == '"'150 if nextchar != '"':151 if nextchar in _ws:152 end = _w(s, end).end()153 nextchar = s[end:end + 1]154 # Trivial empty object155 if nextchar == '}':156 if object_pairs_hook is not None:157 result = object_pairs_hook(pairs)158 return result, end + 1159 pairs = {}160 if object_hook is not None:161 pairs = object_hook(pairs)162 return pairs, end + 1163 elif nextchar != '"':164 raise JSONDecodeError(165 "Expecting property name enclosed in double quotes", s, end)166 end += 1167 while True:168 key, end = scanstring(s, end, strict)169 key = memo_get(key, key)170 # To skip some function call overhead we optimize the fast paths where171 # the JSON key separator is ": " or just ":".172 if s[end:end + 1] != ':':173 end = _w(s, end).end()174 if s[end:end + 1] != ':':175 raise JSONDecodeError("Expecting ':' delimiter", s, end)176 end += 1177 178 try:179 if s[end] in _ws:180 end += 1181 if s[end] in _ws:182 end = _w(s, end + 1).end()183 except IndexError:184 pass185 186 try:187 value, end = scan_once(s, end)188 except StopIteration as err:189 raise JSONDecodeError("Expecting value", s, err.value) from None190 pairs_append((key, value))191 try:192 nextchar = s[end]193 if nextchar in _ws:194 end = _w(s, end + 1).end()195 nextchar = s[end]196 except IndexError:197 nextchar = ''198 end += 1199 200 if nextchar == '}':201 break202 elif nextchar != ',':203 raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)204 comma_idx = end - 1205 end = _w(s, end).end()206 nextchar = s[end:end + 1]207 end += 1208 if nextchar != '"':209 if nextchar == '}':210 raise JSONDecodeError("Illegal trailing comma before end of object", s, comma_idx)211 raise JSONDecodeError(212 "Expecting property name enclosed in double quotes", s, end - 1)213 if object_pairs_hook is not None:214 result = object_pairs_hook(pairs)215 return result, end216 pairs = dict(pairs)217 if object_hook is not None:218 pairs = object_hook(pairs)219 return pairs, end220 221def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):222 s, end = s_and_end223 values = []224 nextchar = s[end:end + 1]225 if nextchar in _ws:226 end = _w(s, end + 1).end()227 nextchar = s[end:end + 1]228 # Look-ahead for trivial empty array229 if nextchar == ']':230 return values, end + 1231 _append = values.append232 while True:233 try:234 value, end = scan_once(s, end)235 except StopIteration as err:236 raise JSONDecodeError("Expecting value", s, err.value) from None237 _append(value)238 nextchar = s[end:end + 1]239 if nextchar in _ws:240 end = _w(s, end + 1).end()241 nextchar = s[end:end + 1]242 end += 1243 if nextchar == ']':244 break245 elif nextchar != ',':246 raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)247 comma_idx = end - 1248 try:249 if s[end] in _ws:250 end += 1251 if s[end] in _ws:252 end = _w(s, end + 1).end()253 nextchar = s[end:end + 1]254 except IndexError:255 pass256 if nextchar == ']':257 raise JSONDecodeError("Illegal trailing comma before end of array", s, comma_idx)258 259 return values, end260 261 262class JSONDecoder(object):263 """Simple JSON <https://json.org> decoder264 265 Performs the following translations in decoding by default:266 267 +---------------+-------------------+268 | JSON | Python |269 +===============+===================+270 | object | dict |271 +---------------+-------------------+272 | array | list |273 +---------------+-------------------+274 | string | str |275 +---------------+-------------------+276 | number (int) | int |277 +---------------+-------------------+278 | number (real) | float |279 +---------------+-------------------+280 | true | True |281 +---------------+-------------------+282 | false | False |283 +---------------+-------------------+284 | null | None |285 +---------------+-------------------+286 287 It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as288 their corresponding ``float`` values, which is outside the JSON spec.289 290 """291 292 def __init__(self, *, object_hook=None, parse_float=None,293 parse_int=None, parse_constant=None, strict=True,294 object_pairs_hook=None):295 """``object_hook``, if specified, will be called with the result296 of every JSON object decoded and its return value will be used in297 place of the given ``dict``. This can be used to provide custom298 deserializations (e.g. to support JSON-RPC class hinting).299 300 ``object_pairs_hook``, if specified will be called with the result301 of every JSON object decoded with an ordered list of pairs. The302 return value of ``object_pairs_hook`` will be used instead of the303 ``dict``. This feature can be used to implement custom decoders.304 If ``object_hook`` is also defined, the ``object_pairs_hook`` takes305 priority.306 307 ``parse_float``, if specified, will be called with the string308 of every JSON float to be decoded. By default this is equivalent to309 float(num_str). This can be used to use another datatype or parser310 for JSON floats (e.g. decimal.Decimal).311 312 ``parse_int``, if specified, will be called with the string313 of every JSON int to be decoded. By default this is equivalent to314 int(num_str). This can be used to use another datatype or parser315 for JSON integers (e.g. float).316 317 ``parse_constant``, if specified, will be called with one of the318 following strings: -Infinity, Infinity, NaN.319 This can be used to raise an exception if invalid JSON numbers320 are encountered.321 322 If ``strict`` is false (true is the default), then control323 characters will be allowed inside strings. Control characters in324 this context are those with character codes in the 0-31 range,325 including ``'\\t'`` (tab), ``'\\n'``, ``'\\r'`` and ``'\\0'``.326 """327 self.object_hook = object_hook328 self.parse_float = parse_float or float329 self.parse_int = parse_int or int330 self.parse_constant = parse_constant or _CONSTANTS.__getitem__331 self.strict = strict332 self.object_pairs_hook = object_pairs_hook333 self.parse_object = JSONObject334 self.parse_array = JSONArray335 self.parse_string = scanstring336 self.memo = {}337 self.scan_once = scanner.make_scanner(self)338 339 340 def decode(self, s, _w=WHITESPACE.match):341 """Return the Python representation of ``s`` (a ``str`` instance342 containing a JSON document).343 344 """345 obj, end = self.raw_decode(s, idx=_w(s, 0).end())346 end = _w(s, end).end()347 if end != len(s):348 raise JSONDecodeError("Extra data", s, end)349 return obj350 351 def raw_decode(self, s, idx=0):352 """Decode a JSON document from ``s`` (a ``str`` beginning with353 a JSON document) and return a 2-tuple of the Python354 representation and the index in ``s`` where the document ended.355 356 This can be used to decode a JSON document from a string that may357 have extraneous data at the end.358 359 """360 try:361 obj, end = self.scan_once(s, idx)362 except StopIteration as err:363 raise JSONDecodeError("Expecting value", s, err.value) from None364 return obj, end365 