Tachyon /Users/simon/.local/share/uv/python/cpython-3.15.0-macos-aarch64-none/lib/python3.15/json/decoder.py
135
Self Samples
270
Cumulative
2
Lines Hit
0.93%
% of Total
135
Max Self
135
Max Total
Intensity:
Cold Hot
Self Time
Total Time
Show All
Hot Only
Heat
Specialization
Line
Self
Total
Code
1
"""Implementation of JSONDecoder
2
"""
3
import re
4
5
from json import scanner
6
try:
7
from _json import scanstring as c_scanstring
8
except ImportError:
9
c_scanstring = None
10
11
__all__ = ['JSONDecoder', 'JSONDecodeError']
12
13
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
14
15
NaN = float('nan')
16
PosInf = float('inf')
17
NegInf = float('-inf')
18
19
20
class JSONDecodeError(ValueError):
21
"""Subclass of ValueError with the following additional properties:
22
23
msg: The unformatted error message
24
doc: The JSON document being parsed
25
pos: The start index of doc where parsing failed
26
lineno: The line corresponding to pos
27
colno: The column corresponding to pos
28
29
"""
30
# Note that this exception is used from _json
31
def __init__(self, msg, doc, pos):
32
lineno = doc.count('\n', 0, pos) + 1
33
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 = msg
37
self.doc = doc
38
self.pos = pos
39
self.lineno = lineno
40
self.colno = colno
41
42
def __reduce__(self):
43
return self.__class__, (self.msg, self.doc, self.pos)
44
45
46
_CONSTANTS = frozendict({
47
'-Infinity': NegInf,
48
'Infinity': PosInf,
49
'NaN': NaN,
50
})
51
52
53
HEXDIGITS = re.compile(r'[0-9A-Fa-f]{4}', FLAGS)
54
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
55
BACKSLASH = {
56
'"': '"', '\\': '\\', '/': '/',
57
'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t',
58
}
59
60
def _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
pass
67
msg = "Invalid \\uXXXX escape"
68
raise JSONDecodeError(msg, s, pos)
69
70
def 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 the
73
character in s after the quote that started the JSON string.
74
Unescapes all valid JSON string escape sequences and raises ValueError
75
on attempt to decode an invalid string. If strict is False then literal
76
control characters are allowed in the string.
77
78
Returns a tuple of the decoded string and the index of the character in s
79
after the end quote."""
80
chunks = []
81
_append = chunks.append
82
begin = end - 1
83
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 characters
90
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 follows
94
if terminator == '"':
95
break
96
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 - 1)
101
else:
102
_append(terminator)
103
continue
104
try:
105
esc = s[end]
106
except IndexError:
107
raise JSONDecodeError("Unterminated string starting at",
108
s, begin) from None
109
# If not a unicode escape sequence, must be in the lookup table
110
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 += 1
117
else:
118
uni = _decode_uXXXX(s, end)
119
end += 5
120
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 += 6
125
char = chr(uni)
126
_append(char)
127
return ''.join(chunks), end
128
129
130
# Use speedup if available
131
scanstring = c_scanstring or py_scanstring
132
133
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
134
WHITESPACE_STR = ' \t\n\r'
135
136
137
def 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_end
140
pairs = []
141
pairs_append = pairs.append
142
# Backwards compatibility
143
if memo is None:
144
memo = {}
145
memo_get = memo.setdefault
146
# Use a slice to prevent IndexError from being raised, the following
147
# check will raise a more specific ValueError if the string is empty
148
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 object
155
if nextchar == '}':
156
if object_pairs_hook is not None:
157
result = object_pairs_hook(pairs)
158
return result, end + 1
159
pairs = {}
160
if object_hook is not None:
161
pairs = object_hook(pairs)
162
return pairs, end + 1
163
elif nextchar != '"':
164
raise JSONDecodeError(
165
"Expecting property name enclosed in double quotes", s, end)
166
end += 1
167
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 where
171
# 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 += 1
177
178
try:
179
if s[end] in _ws:
180
end += 1
181
if s[end] in _ws:
182
end = _w(s, end + 1).end()
183
except IndexError:
184
pass
185
186
try:
187
value, end = scan_once(s, end)
188
except StopIteration as err:
189
raise JSONDecodeError("Expecting value", s, err.value) from None
190
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 += 1
199
200
if nextchar == '}':
201
break
202
elif nextchar != ',':
203
raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
204
comma_idx = end - 1
205
end = _w(s, end).end()
206
nextchar = s[end:end + 1]
207
end += 1
208
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, end
216
pairs = dict(pairs)
217
if object_hook is not None:
218
pairs = object_hook(pairs)
219
return pairs, end
220
221
def JSONArray(s_and_end, scan_once, array_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
222
s, end = s_and_end
223
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 array
229
if nextchar == ']':
230
if array_hook is not None:
231
values = array_hook(values)
232
return values, end + 1
233
_append = values.append
234
while True:
235
try:
236
value, end = scan_once(s, end)
237
except StopIteration as err:
238
raise JSONDecodeError("Expecting value", s, err.value) from None
239
_append(value)
240
nextchar = s[end:end + 1]
241
if nextchar in _ws:
242
end = _w(s, end + 1).end()
243
nextchar = s[end:end + 1]
244
end += 1
245
if nextchar == ']':
246
break
247
elif nextchar != ',':
248
raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
249
comma_idx = end - 1
250
try:
251
if s[end] in _ws:
252
end += 1
253
if s[end] in _ws:
254
end = _w(s, end + 1).end()
255
nextchar = s[end:end + 1]
256
except IndexError:
257
pass
258
if nextchar == ']':
259
raise JSONDecodeError("Illegal trailing comma before end of array", s, comma_idx)
260
261
if array_hook is not None:
262
values = array_hook(values)
263
return values, end
264
265
266
class JSONDecoder(object):
267
"""Simple JSON <https://json.org> decoder
268
269
Performs the following translations in decoding by default:
270
271
+---------------+-------------------+
272
| JSON | Python |
273
+===============+===================+
274
| object | dict |
275
+---------------+-------------------+
276
| array | list |
277
+---------------+-------------------+
278
| string | str |
279
+---------------+-------------------+
280
| number (int) | int |
281
+---------------+-------------------+
282
| number (real) | float |
283
+---------------+-------------------+
284
| true | True |
285
+---------------+-------------------+
286
| false | False |
287
+---------------+-------------------+
288
| null | None |
289
+---------------+-------------------+
290
291
It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
292
their corresponding ``float`` values, which is outside the JSON spec.
293
294
"""
295
296
def __init__(self, *, object_hook=None, parse_float=None,
297
parse_int=None, parse_constant=None, strict=True,
298
object_pairs_hook=None, array_hook=None):
299
"""``object_hook``, if specified, will be called with the result
300
of every JSON object decoded and its return value will be used in
301
place of the given ``dict``. This can be used to provide custom
302
deserializations (e.g. to support JSON-RPC class hinting).
303
304
``object_pairs_hook``, if specified will be called with the result
305
of every JSON object decoded with an ordered list of pairs. The
306
return value of ``object_pairs_hook`` will be used instead of the
307
``dict``. This feature can be used to implement custom decoders.
308
If ``object_hook`` is also defined, the ``object_pairs_hook`` takes
309
priority.
310
311
``array_hook`` is an optional function that will be called with the
312
result of any literal array decode (a ``list``). The return value of
313
this function will be used instead of the ``list``. This feature can
314
be used along ``object_pairs_hook`` to customize the resulting data
315
structure - for example, by setting that to ``frozendict`` and
316
``array_hook`` to ``tuple``, one can get a deep immutable data
317
structute from any JSON data.
318
319
``parse_float``, if specified, will be called with the string
320
of every JSON float to be decoded. By default this is equivalent to
321
float(num_str). This can be used to use another datatype or parser
322
for JSON floats (e.g. decimal.Decimal).
323
324
``parse_int``, if specified, will be called with the string
325
of every JSON int to be decoded. By default this is equivalent to
326
int(num_str). This can be used to use another datatype or parser
327
for JSON integers (e.g. float).
328
329
``parse_constant``, if specified, will be called with one of the
330
following strings: -Infinity, Infinity, NaN.
331
This can be used to raise an exception if invalid JSON numbers
332
are encountered.
333
334
If ``strict`` is false (true is the default), then control
335
characters will be allowed inside strings. Control characters in
336
this context are those with character codes in the 0-31 range,
337
including ``'\\t'`` (tab), ``'\\n'``, ``'\\r'`` and ``'\\0'``.
338
"""
339
self.object_hook = object_hook
340
self.parse_float = parse_float or float
341
self.parse_int = parse_int or int
342
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
343
self.strict = strict
344
self.object_pairs_hook = object_pairs_hook
345
self.array_hook = array_hook
346
self.parse_object = JSONObject
347
self.parse_array = JSONArray
348
self.parse_string = scanstring
349
self.memo = {}
350
self.scan_once = scanner.make_scanner(self)
351
352
353
def decode(self, s, _w=WHITESPACE.match):
354
"""Return the Python representation of ``s`` (a ``str`` instance
355
containing a JSON document).
356
357
"""
358
135
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
359
end = _w(s, end).end()
360
if end != len(s):
361
raise JSONDecodeError("Extra data", s, end)
362
return obj
363
364
def raw_decode(self, s, idx=0):
365
"""Decode a JSON document from ``s`` (a ``str`` beginning with
366
a JSON document) and return a 2-tuple of the Python
367
representation and the index in ``s`` where the document ended.
368
369
This can be used to decode a JSON document from a string that may
370
have extraneous data at the end.
371
372
"""
373
try:
374
135
135
obj, end = self.scan_once(s, idx)
375
except StopIteration as err:
376
raise JSONDecodeError("Expecting value", s, err.value) from None
377
return obj, end