serde.json
Serialize and Deserialize in JSON format.
1""" 2Serialize and Deserialize in JSON format. 3""" 4 5from typing import Any, AnyStr, overload, cast 6 7from .compat import SerdeError, T 8from .de import Deserializer, from_dict 9from .se import Serializer, to_dict 10 11# Lazy numpy imports to improve startup time 12 13try: # pragma: no cover 14 import orjson 15 16 def json_dumps(obj: Any, **opts: Any) -> str: 17 if "option" in opts: 18 opts["option"] |= orjson.OPT_NON_STR_KEYS 19 else: 20 opts["option"] = orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NON_STR_KEYS 21 return cast(str, orjson.dumps(obj, **opts).decode()) 22 23 def json_loads(s: str | bytes, **opts: Any) -> Any: 24 return orjson.loads(s, **opts) 25 26except ImportError: 27 import json 28 29 def json_dumps(obj: Any, **opts: Any) -> str: 30 if "default" not in opts: 31 from .numpy import encode_numpy 32 33 opts["default"] = encode_numpy 34 # compact output 35 ensure_ascii = opts.pop("ensure_ascii", False) 36 separators = opts.pop("separators", (",", ":")) 37 return json.dumps(obj, ensure_ascii=ensure_ascii, separators=separators, **opts) 38 39 def json_loads(s: str | bytes, **opts: Any) -> Any: 40 return json.loads(s, **opts) 41 42 43__all__ = ["from_json", "to_json", "deserialize_json_numbers"] 44 45 46class JsonSerializer(Serializer[str]): 47 @classmethod 48 def serialize(cls, obj: Any, **opts: Any) -> str: 49 return json_dumps(obj, **opts) 50 51 52class JsonDeserializer(Deserializer[AnyStr]): 53 @classmethod 54 def deserialize(cls, data: AnyStr, **opts: Any) -> Any: 55 return json_loads(data, **opts) 56 57 58def deserialize_json_numbers(value: Any) -> float: 59 """ 60 Convert JSON numbers to float, accepting both ints and floats and rejecting non-numeric input. 61 Useful when a JSON payload omits a decimal point but the target field is typed as float. 62 """ 63 if isinstance(value, bool): 64 raise SerdeError(f"Expected JSON number but got boolean {value!r}") 65 if isinstance(value, (int, float)): 66 return float(value) 67 raise SerdeError(f"Expected JSON number but got {type(value).__name__}") 68 69 70def to_json( 71 obj: Any, 72 cls: Any | None = None, 73 se: type[Serializer[str]] = JsonSerializer, 74 reuse_instances: bool = False, 75 convert_sets: bool = True, 76 skip_none: bool = False, 77 **opts: Any, 78) -> str: 79 """ 80 Serialize the object into JSON str. [orjson](https://github.com/ijl/orjson) 81 will be used if installed. 82 83 You can pass any serializable `obj`. If you supply other keyword arguments, 84 they will be passed in `dumps` function. 85 By default, numpy objects are serialized, this behaviour can be customized with the `option` 86 argument with [orjson](https://github.com/ijl/orjson#numpy), or the `default` argument with 87 Python standard json library. 88 89 * `skip_none`: When set to True, any field in the class with a None value is excluded from the 90 serialized output. Defaults to False. 91 92 If you want to use another json package, you can subclass `JsonSerializer` and implement 93 your own logic. 94 """ 95 return se.serialize( 96 to_dict( 97 obj, 98 c=cls, 99 reuse_instances=reuse_instances, 100 convert_sets=convert_sets, 101 skip_none=skip_none, 102 ), 103 **opts, 104 ) 105 106 107@overload 108def from_json( 109 c: type[T], 110 s: AnyStr, 111 de: type[Deserializer[AnyStr]] = JsonDeserializer, 112 coerce_numbers: bool = True, 113 **opts: Any, 114) -> T: ... 115 116 117# For Union, Optional etc. 118@overload 119def from_json( 120 c: Any, 121 s: AnyStr, 122 de: type[Deserializer[AnyStr]] = JsonDeserializer, 123 coerce_numbers: bool = True, 124 **opts: Any, 125) -> Any: ... 126 127 128def from_json( 129 c: Any, 130 s: AnyStr, 131 de: type[Deserializer[AnyStr]] = JsonDeserializer, 132 coerce_numbers: bool = True, 133 **opts: Any, 134) -> Any: 135 """ 136 Deserialize from JSON into the object. [orjson](https://github.com/ijl/orjson) will be used 137 if installed. 138 139 `c` is a class object and `s` is JSON bytes or str. If you supply other keyword arguments, 140 they will be passed in `loads` function. 141 142 * `coerce_numbers`: When True (default), ints from JSON are coerced to floats when the target 143 type is float. Strings are never coerced. 144 145 If you want to use another json package, you can subclass `JsonDeserializer` and implement your 146 own logic. 147 """ 148 deserialize_numbers = deserialize_json_numbers if coerce_numbers else None 149 return from_dict( 150 c, 151 de.deserialize(s, **opts), 152 reuse_instances=False, 153 deserialize_numbers=deserialize_numbers, 154 )
129def from_json( 130 c: Any, 131 s: AnyStr, 132 de: type[Deserializer[AnyStr]] = JsonDeserializer, 133 coerce_numbers: bool = True, 134 **opts: Any, 135) -> Any: 136 """ 137 Deserialize from JSON into the object. [orjson](https://github.com/ijl/orjson) will be used 138 if installed. 139 140 `c` is a class object and `s` is JSON bytes or str. If you supply other keyword arguments, 141 they will be passed in `loads` function. 142 143 * `coerce_numbers`: When True (default), ints from JSON are coerced to floats when the target 144 type is float. Strings are never coerced. 145 146 If you want to use another json package, you can subclass `JsonDeserializer` and implement your 147 own logic. 148 """ 149 deserialize_numbers = deserialize_json_numbers if coerce_numbers else None 150 return from_dict( 151 c, 152 de.deserialize(s, **opts), 153 reuse_instances=False, 154 deserialize_numbers=deserialize_numbers, 155 )
Deserialize from JSON into the object. orjson will be used if installed.
c is a class object and s is JSON bytes or str. If you supply other keyword arguments,
they will be passed in loads function.
coerce_numbers: When True (default), ints from JSON are coerced to floats when the target type is float. Strings are never coerced.
If you want to use another json package, you can subclass JsonDeserializer and implement your
own logic.
71def to_json( 72 obj: Any, 73 cls: Any | None = None, 74 se: type[Serializer[str]] = JsonSerializer, 75 reuse_instances: bool = False, 76 convert_sets: bool = True, 77 skip_none: bool = False, 78 **opts: Any, 79) -> str: 80 """ 81 Serialize the object into JSON str. [orjson](https://github.com/ijl/orjson) 82 will be used if installed. 83 84 You can pass any serializable `obj`. If you supply other keyword arguments, 85 they will be passed in `dumps` function. 86 By default, numpy objects are serialized, this behaviour can be customized with the `option` 87 argument with [orjson](https://github.com/ijl/orjson#numpy), or the `default` argument with 88 Python standard json library. 89 90 * `skip_none`: When set to True, any field in the class with a None value is excluded from the 91 serialized output. Defaults to False. 92 93 If you want to use another json package, you can subclass `JsonSerializer` and implement 94 your own logic. 95 """ 96 return se.serialize( 97 to_dict( 98 obj, 99 c=cls, 100 reuse_instances=reuse_instances, 101 convert_sets=convert_sets, 102 skip_none=skip_none, 103 ), 104 **opts, 105 )
Serialize the object into JSON str. orjson will be used if installed.
You can pass any serializable obj. If you supply other keyword arguments,
they will be passed in dumps function.
By default, numpy objects are serialized, this behaviour can be customized with the option
argument with orjson, or the default argument with
Python standard json library.
skip_none: When set to True, any field in the class with a None value is excluded from the serialized output. Defaults to False.
If you want to use another json package, you can subclass JsonSerializer and implement
your own logic.
59def deserialize_json_numbers(value: Any) -> float: 60 """ 61 Convert JSON numbers to float, accepting both ints and floats and rejecting non-numeric input. 62 Useful when a JSON payload omits a decimal point but the target field is typed as float. 63 """ 64 if isinstance(value, bool): 65 raise SerdeError(f"Expected JSON number but got boolean {value!r}") 66 if isinstance(value, (int, float)): 67 return float(value) 68 raise SerdeError(f"Expected JSON number but got {type(value).__name__}")
Convert JSON numbers to float, accepting both ints and floats and rejecting non-numeric input. Useful when a JSON payload omits a decimal point but the target field is typed as float.