How-to Guide¶
Encoding and decoding protocol-based objects¶
Added in version 2.0.
Encoding protocol-based objects¶
Changed in version 2.4: Made frozendict serializable by default.
By default, protocol types are encoded from instances of the corresponding
Python types shown below. Additional types can be registered with types.
Type |
Default |
Required methods |
|---|---|---|
|
|
|
|
|
|
|
||
|
||
|
|
none (not extensible) |
|
|
|
|
Example with numpy:
>>> import jsonyx as json
>>> import numpy as np
>>> obj = np.array([
... np.bool_(), np.int8(), np.uint8(), np.int16(), np.uint16(), np.int32(),
... np.uint32(), np.intp(), np.uintp(), np.int64(), np.uint64(), np.float16(),
... np.float32(), np.float64()
... ], dtype="O")
>>> types = {
... "array": np.ndarray,
... "bool": np.bool_,
... "float": np.floating,
... "int": np.integer
... }
>>> json.dump(obj, types=types)
[false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0]
Note
Custom types must be registered manually, jsonyx does not
infer serializability based on method presence.
Warning
Avoid specifying ABCs for types, that is very slow.
Decoding protocol-based objects¶
By default, decoded protocol types are converted to the corresponding Python
types shown below. The conversion can be customized with hooks.
Hook |
Default |
Called with |
|---|---|---|
|
||
|
|
|
|
||
|
||
|
nothing (not customizable) |
|
|
|
|
|
Example with numpy:
>>> import jsonyx as json
>>> from functools import partial
>>> import numpy as np
>>> hooks = {
... "array": partial(np.array, dtype="O"),
... "bool": np.bool_,
... "float": np.float64,
... "int": np.int64
... }
>>> json.loads("[false, 0.0, 0]", hooks=hooks)
array([np.False_, np.float64(0.0), np.int64(0)], dtype=object)
Using decimal.Decimal instead of float¶
>>> import jsonyx as json
>>> from decimal import Decimal
>>> json.loads("1.1", hooks={"float": Decimal})
Decimal('1.1')
>>> json.dump(Decimal('1.1'), types={"float": Decimal})
1.1
Using multidict.MultiDict instead of dict¶
>>> import jsonyx as json
>>> from multidict import MultiDict
>>> json.loads('{"a": 1, "a": 2, "a": 3}', hooks={"object": MultiDict})
<MultiDict('a': 1, 'a': 2, 'a': 3)>
>>> obj = MultiDict([('a', 1), ('a', 2), ('a', 3)])
>>> json.dump(obj, types={"object": MultiDict})
{"a": 1, "a": 2, "a": 3}
Encoding and decoding arbitrary objects¶
Added in version 2.1.
Encoding arbitrary objects¶
>>> import jsonyx as json
>>> def complex_hook(obj):
... if isinstance(obj, complex):
... return {"__complex__": True, "real": obj.real, "imag": obj.imag}
... return obj
...
>>> json.dump(1 + 2j, hook=complex_hook)
{"__complex__": true, "real": 1.0, "imag": 2.0}
Tip
Use functools.singledispatch() to make this extensible.
Warning
This function is called for every object during encoding, even if the object is normally serializable.
Decoding arbitrary objects¶
>>> import jsonyx as json
>>> def object_hook(obj):
... obj = dict(obj)
... if "__complex__" in obj:
... return complex(obj["real"], obj["imag"])
... return obj
...
>>> s = '{"__complex__": true, "real": 1.0, "imag": 2.0}'
>>> json.loads(s, hooks={"object": object_hook})
(1+2j)
Note
hooks["object"] is called with a list of tuples, not a dict.
Formatting numbers¶
Added in version 2.4.
By default, numbers are formatted using str. Custom formatters can
be registered with formatters. Formatters must return a string containing
a valid JSON number.
Example:
>>> import jsonyx as json
>>> json.dump([1.234, 5.678], formatters={"float": "{:.2f}".format})
[1.23, 5.68]
Encoding enum.ReprEnum¶
>>> import jsonyx as json
>>> from enum import ReprEnum
>>> class MyEnum(int, ReprEnum):
... ZERO = 0
...
>>> json.dump(MyEnum.ZERO)
0
Warning
Don’t use enum.Enum, because it overrides the string
representation.
Encoding and decoding big integers¶
Python has a global limit for converting between int and str.
If you need to process integers exceeding this limit, use
sys.set_int_max_str_digits() to increase it:
>>> import jsonyx as json
>>> from sys import set_int_max_str_digits
>>> set_int_max_str_digits(0)
>>> json.loads("9" * 5_000) == 10 ** 5_000 - 1
True
>>> len(json.dumps(10 ** 5_000))
5002
See Integer string conversion length limitation for more information.
Better error messages¶
Better error messages for other JSON libraries¶
>>> import json, jsonyx
>>> try:
... json.loads("[,]")
... except json.JSONDecodeError as exc:
... raise jsonyx.JSONSyntaxError(exc.msg, "<string>", exc.doc, exc.pos) from None
...
Traceback (most recent call last):
File "<string>", line 1, column 2
[,]
^
jsonyx.JSONSyntaxError: Expecting value
Better error messages for encoding strings¶
>>> import jsonyx as json
>>> try:
... "café".encode("ascii")
... except UnicodeEncodeError as exc:
... raise json.TruncatedSyntaxError(
... f"(unicode error) {exc}", "<string>", exc.object, exc.start, exc.end,
... ) from None
...
Traceback (most recent call last):
File "<string>", line 1, column 4-5
café
^
jsonyx.TruncatedSyntaxError: (unicode error) 'ascii' codec can't encode character '\xe9' in position 3: ordinal not in range(128)
Better error messages for decoding bytes¶
>>> import jsonyx as json
>>> try:
... b"caf\xe9".decode("ascii")
... except UnicodeDecodeError as exc:
... doc = exc.object.decode(exc.encoding, "replace")
... start = exc.object[:exc.start].decode(exc.encoding, "replace")
... end = exc.object[:exc.end].decode(exc.encoding, "replace")
... raise json.TruncatedSyntaxError(
... f"(unicode error) {exc}", "<string>", doc, len(start), len(end),
... ) from None
...
Traceback (most recent call last):
File "<string>", line 1, column 4-5
caf�
^
jsonyx.TruncatedSyntaxError: (unicode error) 'ascii' codec can't decode byte 0xe9 in position 3: ordinal not in range(128)
See also
jsonyx.format_syntax_error() for formatting the exception.