codekingpro/portable-devtools
114k
1"""Implementation of JSONEncoder2"""3import re4 5try:6 from _json import encode_basestring_ascii as c_encode_basestring_ascii7except ImportError:8 c_encode_basestring_ascii = None9try:10 from _json import encode_basestring as c_encode_basestring11except ImportError:12 c_encode_basestring = None13try:14 from _json import make_encoder as c_make_encoder15except ImportError:16 c_make_encoder = None17 18ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')19ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')20HAS_UTF8 = re.compile(b'[\x80-\xff]')21ESCAPE_DCT = {22 '\\': '\\\\',23 '"': '\\"',24 '\b': '\\b',25 '\f': '\\f',26 '\n': '\\n',27 '\r': '\\r',28 '\t': '\\t',29}30for i in range(0x20):31 ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))32 #ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))33del i34 35INFINITY = float('inf')36 37def py_encode_basestring(s):38 """Return a JSON representation of a Python string39 40 """41 def replace(match):42 return ESCAPE_DCT[match.group(0)]43 return '"' + ESCAPE.sub(replace, s) + '"'44 45 46encode_basestring = (c_encode_basestring or py_encode_basestring)47 48 49def py_encode_basestring_ascii(s):50 """Return an ASCII-only JSON representation of a Python string51 52 """53 def replace(match):54 s = match.group(0)55 try:56 return ESCAPE_DCT[s]57 except KeyError:58 n = ord(s)59 if n < 0x10000:60 return '\\u{0:04x}'.format(n)61 #return '\\u%04x' % (n,)62 else:63 # surrogate pair64 n -= 0x1000065 s1 = 0xd800 | ((n >> 10) & 0x3ff)66 s2 = 0xdc00 | (n & 0x3ff)67 return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)68 return '"' + ESCAPE_ASCII.sub(replace, s) + '"'69 70 71encode_basestring_ascii = (72 c_encode_basestring_ascii or py_encode_basestring_ascii)73 74class JSONEncoder(object):75 """Extensible JSON <https://json.org> encoder for Python data structures.76 77 Supports the following objects and types by default:78 79 +-------------------+---------------+80 | Python | JSON |81 +===================+===============+82 | dict | object |83 +-------------------+---------------+84 | list, tuple | array |85 +-------------------+---------------+86 | str | string |87 +-------------------+---------------+88 | int, float | number |89 +-------------------+---------------+90 | True | true |91 +-------------------+---------------+92 | False | false |93 +-------------------+---------------+94 | None | null |95 +-------------------+---------------+96 97 To extend this to recognize other objects, subclass and implement a98 ``.default()`` method with another method that returns a serializable99 object for ``o`` if possible, otherwise it should call the superclass100 implementation (to raise ``TypeError``).101 102 """103 item_separator = ', '104 key_separator = ': '105 def __init__(self, *, skipkeys=False, ensure_ascii=True,106 check_circular=True, allow_nan=True, sort_keys=False,107 indent=None, separators=None, default=None):108 """Constructor for JSONEncoder, with sensible defaults.109 110 If skipkeys is false, then it is a TypeError to attempt111 encoding of keys that are not str, int, float, bool or None.112 If skipkeys is True, such items are simply skipped.113 114 If ensure_ascii is true, the output is guaranteed to be str objects115 with all incoming non-ASCII and non-printable characters escaped.116 If ensure_ascii is false, the output can contain non-ASCII and117 non-printable characters.118 119 If check_circular is true, then lists, dicts, and custom encoded120 objects will be checked for circular references during encoding to121 prevent an infinite recursion (which would cause an RecursionError).122 Otherwise, no such check takes place.123 124 If allow_nan is true, then NaN, Infinity, and -Infinity will be125 encoded as such. This behavior is not JSON specification compliant,126 but is consistent with most JavaScript based encoders and decoders.127 Otherwise, it will be a ValueError to encode such floats.128 129 If sort_keys is true, then the output of dictionaries will be130 sorted by key; this is useful for regression tests to ensure131 that JSON serializations can be compared on a day-to-day basis.132 133 If indent is a non-negative integer, then JSON array134 elements and object members will be pretty-printed with that135 indent level. An indent level of 0 will only insert newlines.136 None is the most compact representation.137 138 If specified, separators should be an (item_separator,139 key_separator) tuple. The default is (', ', ': ') if *indent* is140 ``None`` and (',', ': ') otherwise. To get the most compact JSON141 representation, you should specify (',', ':') to eliminate142 whitespace.143 144 If specified, default is a function that gets called for objects145 that can't otherwise be serialized. It should return a JSON146 encodable version of the object or raise a ``TypeError``.147 148 """149 150 self.skipkeys = skipkeys151 self.ensure_ascii = ensure_ascii152 self.check_circular = check_circular153 self.allow_nan = allow_nan154 self.sort_keys = sort_keys155 self.indent = indent156 if separators is not None:157 self.item_separator, self.key_separator = separators158 elif indent is not None:159 self.item_separator = ','160 if default is not None:161 self.default = default162 163 def default(self, o):164 """Implement this method in a subclass such that it returns165 a serializable object for ``o``, or calls the base implementation166 (to raise a ``TypeError``).167 168 For example, to support arbitrary iterators, you could169 implement default like this::170 171 def default(self, o):172 try:173 iterable = iter(o)174 except TypeError:175 pass176 else:177 return list(iterable)178 # Let the base class default method raise the TypeError179 return super().default(o)180 181 """182 raise TypeError(f'Object of type {o.__class__.__name__} '183 f'is not JSON serializable')184 185 def encode(self, o):186 """Return a JSON string representation of a Python data structure.187 188 >>> from json.encoder import JSONEncoder189 >>> JSONEncoder().encode({"foo": ["bar", "baz"]})190 '{"foo": ["bar", "baz"]}'191 192 """193 # This is for extremely simple cases and benchmarks.194 if isinstance(o, str):195 if self.ensure_ascii:196 return encode_basestring_ascii(o)197 else:198 return encode_basestring(o)199 # This doesn't pass the iterator directly to ''.join() because the200 # exceptions aren't as detailed. The list call should be roughly201 # equivalent to the PySequence_Fast that ''.join() would do.202 chunks = self.iterencode(o, _one_shot=True)203 if not isinstance(chunks, (list, tuple)):204 chunks = list(chunks)205 return ''.join(chunks)206 207 def iterencode(self, o, _one_shot=False):208 """Encode the given object and yield each string209 representation as available.210 211 For example::212 213 for chunk in JSONEncoder().iterencode(bigobject):214 mysocket.write(chunk)215 216 """217 if self.check_circular:218 markers = {}219 else:220 markers = None221 if self.ensure_ascii:222 _encoder = encode_basestring_ascii223 else:224 _encoder = encode_basestring225 226 def floatstr(o, allow_nan=self.allow_nan,227 _repr=float.__repr__, _inf=INFINITY, _neginf=-INFINITY):228 # Check for specials. Note that this type of test is processor229 # and/or platform-specific, so do tests which don't depend on the230 # internals.231 232 if o != o:233 text = 'NaN'234 elif o == _inf:235 text = 'Infinity'236 elif o == _neginf:237 text = '-Infinity'238 else:239 return _repr(o)240 241 if not allow_nan:242 raise ValueError(243 "Out of range float values are not JSON compliant: " +244 repr(o))245 246 return text247 248 249 if self.indent is None or isinstance(self.indent, str):250 indent = self.indent251 else:252 indent = ' ' * self.indent253 if _one_shot and c_make_encoder is not None:254 _iterencode = c_make_encoder(255 markers, self.default, _encoder, indent,256 self.key_separator, self.item_separator, self.sort_keys,257 self.skipkeys, self.allow_nan)258 else:259 _iterencode = _make_iterencode(260 markers, self.default, _encoder, indent, floatstr,261 self.key_separator, self.item_separator, self.sort_keys,262 self.skipkeys, _one_shot)263 return _iterencode(o, 0)264 265def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,266 _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,267 ## HACK: hand-optimized bytecode; turn globals into locals268 ValueError=ValueError,269 dict=dict,270 float=float,271 id=id,272 int=int,273 isinstance=isinstance,274 list=list,275 str=str,276 tuple=tuple,277 _intstr=int.__repr__,278 ):279 280 def _iterencode_list(lst, _current_indent_level):281 if not lst:282 yield '[]'283 return284 if markers is not None:285 markerid = id(lst)286 if markerid in markers:287 raise ValueError("Circular reference detected")288 markers[markerid] = lst289 buf = '['290 if _indent is not None:291 _current_indent_level += 1292 newline_indent = '\n' + _indent * _current_indent_level293 separator = _item_separator + newline_indent294 buf += newline_indent295 else:296 newline_indent = None297 separator = _item_separator298 for i, value in enumerate(lst):299 if i:300 buf = separator301 try:302 if isinstance(value, str):303 yield buf + _encoder(value)304 elif value is None:305 yield buf + 'null'306 elif value is True:307 yield buf + 'true'308 elif value is False:309 yield buf + 'false'310 elif isinstance(value, int):311 # Subclasses of int/float may override __repr__, but we still312 # want to encode them as integers/floats in JSON. One example313 # within the standard library is IntEnum.314 yield buf + _intstr(value)315 elif isinstance(value, float):316 # see comment above for int317 yield buf + _floatstr(value)318 else:319 yield buf320 if isinstance(value, (list, tuple)):321 chunks = _iterencode_list(value, _current_indent_level)322 elif isinstance(value, dict):323 chunks = _iterencode_dict(value, _current_indent_level)324 else:325 chunks = _iterencode(value, _current_indent_level)326 yield from chunks327 except GeneratorExit:328 raise329 except BaseException as exc:330 exc.add_note(f'when serializing {type(lst).__name__} item {i}')331 raise332 if newline_indent is not None:333 _current_indent_level -= 1334 yield '\n' + _indent * _current_indent_level335 yield ']'336 if markers is not None:337 del markers[markerid]338 339 def _iterencode_dict(dct, _current_indent_level):340 if not dct:341 yield '{}'342 return343 if markers is not None:344 markerid = id(dct)345 if markerid in markers:346 raise ValueError("Circular reference detected")347 markers[markerid] = dct348 yield '{'349 if _indent is not None:350 _current_indent_level += 1351 newline_indent = '\n' + _indent * _current_indent_level352 item_separator = _item_separator + newline_indent353 else:354 newline_indent = None355 item_separator = _item_separator356 first = True357 if _sort_keys:358 items = sorted(dct.items())359 else:360 items = dct.items()361 for key, value in items:362 if isinstance(key, str):363 pass364 # JavaScript is weakly typed for these, so it makes sense to365 # also allow them. Many encoders seem to do something like this.366 elif isinstance(key, float):367 # see comment for int/float in _make_iterencode368 key = _floatstr(key)369 elif key is True:370 key = 'true'371 elif key is False:372 key = 'false'373 elif key is None:374 key = 'null'375 elif isinstance(key, int):376 # see comment for int/float in _make_iterencode377 key = _intstr(key)378 elif _skipkeys:379 continue380 else:381 raise TypeError(f'keys must be str, int, float, bool or None, '382 f'not {key.__class__.__name__}')383 if first:384 first = False385 if newline_indent is not None:386 yield newline_indent387 else:388 yield item_separator389 yield _encoder(key)390 yield _key_separator391 try:392 if isinstance(value, str):393 yield _encoder(value)394 elif value is None:395 yield 'null'396 elif value is True:397 yield 'true'398 elif value is False:399 yield 'false'400 elif isinstance(value, int):401 # see comment for int/float in _make_iterencode402 yield _intstr(value)403 elif isinstance(value, float):404 # see comment for int/float in _make_iterencode405 yield _floatstr(value)406 else:407 if isinstance(value, (list, tuple)):408 chunks = _iterencode_list(value, _current_indent_level)409 elif isinstance(value, dict):410 chunks = _iterencode_dict(value, _current_indent_level)411 else:412 chunks = _iterencode(value, _current_indent_level)413 yield from chunks414 except GeneratorExit:415 raise416 except BaseException as exc:417 exc.add_note(f'when serializing {type(dct).__name__} item {key!r}')418 raise419 if not first and newline_indent is not None:420 _current_indent_level -= 1421 yield '\n' + _indent * _current_indent_level422 yield '}'423 if markers is not None:424 del markers[markerid]425 426 def _iterencode(o, _current_indent_level):427 if isinstance(o, str):428 yield _encoder(o)429 elif o is None:430 yield 'null'431 elif o is True:432 yield 'true'433 elif o is False:434 yield 'false'435 elif isinstance(o, int):436 # see comment for int/float in _make_iterencode437 yield _intstr(o)438 elif isinstance(o, float):439 # see comment for int/float in _make_iterencode440 yield _floatstr(o)441 elif isinstance(o, (list, tuple)):442 yield from _iterencode_list(o, _current_indent_level)443 elif isinstance(o, dict):444 yield from _iterencode_dict(o, _current_indent_level)445 else:446 if markers is not None:447 markerid = id(o)448 if markerid in markers:449 raise ValueError("Circular reference detected")450 markers[markerid] = o451 newobj = _default(o)452 try:453 yield from _iterencode(newobj, _current_indent_level)454 except GeneratorExit:455 raise456 except BaseException as exc:457 exc.add_note(f'when serializing {type(o).__name__} object')458 raise459 if markers is not None:460 del markers[markerid]461 return _iterencode462 