Edit on GitHub

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    )
def from_json( c: Any, s: ~AnyStr, de: type[serde.de.Deserializer[~AnyStr]] = <class 'serde.json.JsonDeserializer'>, coerce_numbers: bool = True, **opts: Any) -> Any:
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.

def to_json( obj: Any, cls: typing.Any | None = None, se: type[serde.se.Serializer[str]] = <class 'serde.json.JsonSerializer'>, reuse_instances: bool = False, convert_sets: bool = True, skip_none: bool = False, **opts: Any) -> str:
 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.

def deserialize_json_numbers(value: Any) -> float:
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.