Edit on GitHub

serde

pyserde

Yet another serialization library on top of dataclasses, inspired by serde-rs.

pypi pypi GithubActions CodeCov

GuideπŸ‡¬πŸ‡§ | γ‚¬γ‚€γƒ‰πŸ‡―πŸ‡΅ | API Reference | Examples

Overview

pyserde is a simple yet powerful serialization library on top of dataclasses. It allows you to convert Python objects to and from JSON, YAML, and other formats easily and efficiently.

Declare your class with @serde decorator and annotate fields using PEP484 as below.

@serde
class Foo:
    i: int
    s: str
    f: float
    b: bool

You can serialize Foo object into JSON.

>>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
'{"i":10,"s":"foo","f":100.0,"b":true}'

You can deserialize JSON into Foo object.

>>> from_json(Foo, '{"i": 10, "s": "foo", "f": 100.0, "b": true}')
Foo(i=10, s='foo', f=100.0, b=True)

That's it! If you're interested in pyserde, please check our documentation! Happy coding with pyserde! πŸš€

Features

Extensions

Contributors ✨

Thanks goes to these wonderful people:

Made with contrib.rocks.

LICENSE

This project is licensed under the MIT license.

Modules

The following modules provide the core functionalities of pyserde.

The following modules provide pyserde's (de)serialize APIs.

Other modules

  1"""
  2.. include:: ../README.md
  3
  4## Modules
  5
  6The following modules provide the core functionalities of `pyserde`.
  7* `serde.se`: All about serialization.
  8* `serde.de`: All about deserialization.
  9* `serde.core`: Core module used by `serde.se` and `serde.de` modules.
 10* `serde.compat`: Compatibility layer which handles mostly differences of `typing` module between
 11python versions.
 12
 13The following modules provide pyserde's (de)serialize APIs.
 14* `serde.json`: Serialize and Deserialize in JSON.
 15* `serde.msgpack`: Serialize and Deserialize in MsgPack.
 16* `serde.yaml`: Serialize and Deserialize in YAML.
 17* `serde.toml`: Serialize and Deserialize in TOML.
 18* `serde.pickle`: Serialize and Deserialize in Pickle.
 19
 20Other modules
 21* `serde.inspect`: Prints generated code by pyserde.
 22"""
 23
 24from dataclasses import dataclass
 25from collections.abc import Callable
 26from typing import Any, Type, overload
 27
 28from typing_extensions import dataclass_transform
 29
 30from .compat import SerdeError, SerdeSkip, T
 31from .core import (
 32    ClassSerializer,
 33    ClassDeserializer,
 34    AdjacentTagging,
 35    coerce,
 36    DefaultTagging,
 37    ExternalTagging,
 38    InternalTagging,
 39    disabled,
 40    strict,
 41    Tagging,
 42    TypeCheck,
 43    Untagged,
 44    field,
 45    init,
 46    logger,
 47    should_impl_dataclass,
 48    add_serializer,
 49    add_deserializer,
 50)
 51from .de import (
 52    DeserializeFunc,
 53    default_deserializer,
 54    deserialize,
 55    from_dict,
 56    from_tuple,
 57    is_deserializable,
 58)
 59from .se import (
 60    SerializeFunc,
 61    asdict,
 62    astuple,
 63    default_serializer,
 64    is_serializable,
 65    serialize,
 66    to_dict,
 67    to_tuple,
 68)
 69
 70__all__ = [
 71    "serde",
 72    "serialize",
 73    "deserialize",
 74    "is_serializable",
 75    "is_deserializable",
 76    "to_dict",
 77    "from_dict",
 78    "to_tuple",
 79    "from_tuple",
 80    "SerdeError",
 81    "SerdeSkip",
 82    "AdjacentTagging",
 83    "ExternalTagging",
 84    "InternalTagging",
 85    "Untagged",
 86    "disabled",
 87    "strict",
 88    "coerce",
 89    "field",
 90    "default_deserializer",
 91    "asdict",
 92    "astuple",
 93    "default_serializer",
 94    "compat",
 95    "core",
 96    "de",
 97    "inspect",
 98    "json",
 99    "msgpack",
100    "numpy",
101    "se",
102    "toml",
103    "pickle",
104    "yaml",
105    "init",
106    "logger",
107    "ClassSerializer",
108    "ClassDeserializer",
109    "add_serializer",
110    "add_deserializer",
111]
112
113
114@overload
115def serde(
116    _cls: Type[T],
117    rename_all: str | None = None,
118    reuse_instances_default: bool = True,
119    convert_sets_default: bool = False,
120    serializer: SerializeFunc | None = None,
121    deserializer: DeserializeFunc | None = None,
122    tagging: Tagging = DefaultTagging,
123    type_check: TypeCheck = strict,
124    serialize_class_var: bool = False,
125    class_serializer: ClassSerializer | None = None,
126    class_deserializer: ClassDeserializer | None = None,
127    deny_unknown_fields: bool = False,
128) -> Type[T]: ...
129
130
131@overload
132def serde(
133    _cls: Any = None,
134    rename_all: str | None = None,
135    reuse_instances_default: bool = True,
136    convert_sets_default: bool = False,
137    serializer: SerializeFunc | None = None,
138    deserializer: DeserializeFunc | None = None,
139    tagging: Tagging = DefaultTagging,
140    type_check: TypeCheck = strict,
141    serialize_class_var: bool = False,
142    class_serializer: ClassSerializer | None = None,
143    class_deserializer: ClassDeserializer | None = None,
144    deny_unknown_fields: bool = False,
145) -> Callable[[type[T]], type[T]]: ...
146
147
148@dataclass_transform(field_specifiers=(field,))
149def serde(
150    _cls: Any = None,
151    rename_all: str | None = None,
152    reuse_instances_default: bool = True,
153    convert_sets_default: bool = False,
154    serializer: SerializeFunc | None = None,
155    deserializer: DeserializeFunc | None = None,
156    tagging: Tagging = DefaultTagging,
157    type_check: TypeCheck = strict,
158    serialize_class_var: bool = False,
159    class_serializer: ClassSerializer | None = None,
160    class_deserializer: ClassDeserializer | None = None,
161    deny_unknown_fields: bool = False,
162) -> Any:
163    """
164    serde decorator. Keyword arguments are passed in `serialize` and `deserialize`.
165    """
166
167    def wrap(cls: Any) -> Any:
168        if should_impl_dataclass(cls):
169            dataclass(cls)
170        serialize(
171            cls,
172            rename_all=rename_all,
173            reuse_instances_default=reuse_instances_default,
174            convert_sets_default=convert_sets_default,
175            serializer=serializer,
176            deserializer=deserializer,
177            tagging=tagging,
178            type_check=type_check,
179            serialize_class_var=serialize_class_var,
180            class_serializer=class_serializer,
181        )
182        deserialize(
183            cls,
184            rename_all=rename_all,
185            reuse_instances_default=reuse_instances_default,
186            convert_sets_default=convert_sets_default,
187            serializer=serializer,
188            deserializer=deserializer,
189            tagging=tagging,
190            type_check=type_check,
191            serialize_class_var=serialize_class_var,
192            class_deserializer=class_deserializer,
193            deny_unknown_fields=deny_unknown_fields,
194        )
195        return cls
196
197    if _cls is None:
198        return wrap
199
200    return wrap(_cls)
@dataclass_transform(field_specifiers=(field,))
def serde( _cls: Any = None, rename_all: str | None = None, reuse_instances_default: bool = True, convert_sets_default: bool = False, serializer: Callable[[type[typing.Any], typing.Any], typing.Any] | None = None, deserializer: Callable[[type[typing.Any], typing.Any], typing.Any] | None = None, tagging: serde.core.Tagging = Tagging(tag=None, content=None, kind=<Kind.External: 1>), type_check: serde.core.TypeCheck = TypeCheck(kind=<Kind.Strict: 3>), serialize_class_var: bool = False, class_serializer: ClassSerializer | None = None, class_deserializer: ClassDeserializer | None = None, deny_unknown_fields: bool = False) -> Any:
149@dataclass_transform(field_specifiers=(field,))
150def serde(
151    _cls: Any = None,
152    rename_all: str | None = None,
153    reuse_instances_default: bool = True,
154    convert_sets_default: bool = False,
155    serializer: SerializeFunc | None = None,
156    deserializer: DeserializeFunc | None = None,
157    tagging: Tagging = DefaultTagging,
158    type_check: TypeCheck = strict,
159    serialize_class_var: bool = False,
160    class_serializer: ClassSerializer | None = None,
161    class_deserializer: ClassDeserializer | None = None,
162    deny_unknown_fields: bool = False,
163) -> Any:
164    """
165    serde decorator. Keyword arguments are passed in `serialize` and `deserialize`.
166    """
167
168    def wrap(cls: Any) -> Any:
169        if should_impl_dataclass(cls):
170            dataclass(cls)
171        serialize(
172            cls,
173            rename_all=rename_all,
174            reuse_instances_default=reuse_instances_default,
175            convert_sets_default=convert_sets_default,
176            serializer=serializer,
177            deserializer=deserializer,
178            tagging=tagging,
179            type_check=type_check,
180            serialize_class_var=serialize_class_var,
181            class_serializer=class_serializer,
182        )
183        deserialize(
184            cls,
185            rename_all=rename_all,
186            reuse_instances_default=reuse_instances_default,
187            convert_sets_default=convert_sets_default,
188            serializer=serializer,
189            deserializer=deserializer,
190            tagging=tagging,
191            type_check=type_check,
192            serialize_class_var=serialize_class_var,
193            class_deserializer=class_deserializer,
194            deny_unknown_fields=deny_unknown_fields,
195        )
196        return cls
197
198    if _cls is None:
199        return wrap
200
201    return wrap(_cls)

serde decorator. Keyword arguments are passed in serialize and deserialize.

@dataclass_transform()
def serialize( _cls: type[~T] | None = None, rename_all: str | None = None, reuse_instances_default: bool = False, convert_sets_default: bool = False, serializer: Callable[[type[typing.Any], typing.Any], typing.Any] | None = None, tagging: serde.core.Tagging = Tagging(tag=None, content=None, kind=<Kind.External: 1>), type_check: serde.core.TypeCheck = TypeCheck(kind=<Kind.Strict: 3>), serialize_class_var: bool = False, class_serializer: ClassSerializer | None = None, **kwargs: Any) -> type[~T]:
188@dataclass_transform()
189def serialize(
190    _cls: type[T] | None = None,
191    rename_all: str | None = None,
192    reuse_instances_default: bool = False,
193    convert_sets_default: bool = False,
194    serializer: SerializeFunc | None = None,
195    tagging: Tagging = DefaultTagging,
196    type_check: TypeCheck = strict,
197    serialize_class_var: bool = False,
198    class_serializer: ClassSerializer | None = None,
199    **kwargs: Any,
200) -> type[T]:
201    """
202    A dataclass with this decorator is serializable into any of the data formats
203    supported by pyserde.
204
205    >>> from datetime import datetime
206    >>> from serde import serialize
207    >>> from serde.json import to_json
208    >>>
209    >>> @serialize
210    ... class Foo:
211    ...     i: int
212    ...     s: str
213    ...     f: float
214    ...     b: bool
215    >>>
216    >>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
217    '{"i":10,"s":"foo","f":100.0,"b":true}'
218    """
219
220    def wrap(cls: type[T]) -> type[T]:
221        tagging.check()
222
223        # If no `dataclass` found in the class, dataclassify it automatically.
224        if not is_dataclass(cls):
225            dataclass(cls)
226
227        if type_check.is_strict():
228            serde_beartype = beartype(conf=BeartypeConf(violation_type=SerdeError))
229            serde_beartype(cls)
230
231        g: dict[str, Any] = {}
232
233        # Create a scope storage used by serde.
234        # Each class should get own scope. Child classes can not share scope with parent class.
235        # That's why we need the "scope.cls is not cls" check.
236        scope: Scope | None = getattr(cls, SERDE_SCOPE, None)
237        if scope is None or scope.cls is not cls:
238            scope = Scope(
239                cls,
240                reuse_instances_default=reuse_instances_default,
241                convert_sets_default=convert_sets_default,
242            )
243            setattr(cls, SERDE_SCOPE, scope)
244
245        class_serializers: list[ClassSerializer] = list(
246            itertools.chain(GLOBAL_CLASS_SERIALIZER, [class_serializer] if class_serializer else [])
247        )
248
249        # Set some globals for all generated functions
250        g["cls"] = cls
251        g["copy"] = copy
252        g["serde_scope"] = scope
253        g["SerdeError"] = SerdeError
254        g["raise_unsupported_type"] = raise_unsupported_type
255        g["enum_value"] = enum_value
256        g["is_dataclass"] = is_dataclass
257        g["typename"] = typename  # used in union functions
258        g["is_instance"] = is_instance  # used in union functions
259        g["to_obj"] = to_obj
260        g["typing"] = typing
261        g["Literal"] = Literal
262        g["TypeCheck"] = TypeCheck
263        g["disabled"] = disabled
264        g["coerce_object"] = coerce_object
265        g["class_serializers"] = class_serializers
266        if serializer:
267            g["serde_legacy_custom_class_serializer"] = functools.partial(
268                serde_legacy_custom_class_serializer, custom=serializer
269            )
270
271        # Collect types used in the generated code.
272        for typ in iter_types(cls):
273            # When we encounter a dataclass not marked with serialize, then also generate serialize
274            # functions for it.
275            if is_dataclass_without_se(typ) and typ is not cls:
276                # We call serialize and not wrap to make sure that we will use the default serde
277                # configuration for generating the serialization function.
278                serialize(typ)
279
280            if is_primitive(typ) and not is_enum(typ):
281                continue
282            g[typename(typ)] = typ
283
284        # render all union functions
285        for union in iter_unions(cls):
286            union_args = list(type_args(union))
287            union_key = union_func_name(UNION_SE_PREFIX, union_args)
288            add_func(scope, union_key, render_union_func(cls, union_args, tagging), g)
289            scope.union_se_args[union_key] = union_args
290
291        for f in sefields(cls, serialize_class_var):
292            if f.skip_if:
293                g[f.skip_if.name] = f.skip_if
294            if f.serializer:
295                g[f.serializer.name] = f.serializer
296
297        add_func(
298            scope,
299            TO_ITER,
300            render_to_tuple(cls, serializer, type_check, serialize_class_var, class_serializer),
301            g,
302        )
303        add_func(
304            scope,
305            TO_DICT,
306            render_to_dict(
307                cls, rename_all, serializer, type_check, serialize_class_var, class_serializer
308            ),
309            g,
310        )
311
312        logger.debug(f"{typename(cls)}: {SERDE_SCOPE} {scope}")
313
314        return cls
315
316    if _cls is None:
317        return wrap  # type: ignore
318
319    if _cls in GENERATION_STACK:
320        return _cls
321
322    GENERATION_STACK.append(_cls)
323    try:
324        return wrap(_cls)
325    finally:
326        GENERATION_STACK.pop()

A dataclass with this decorator is serializable into any of the data formats supported by pyserde.

>>> from datetime import datetime
>>> from serde import serialize
>>> from serde.json import to_json
>>>
>>> @serialize
... class Foo:
...     i: int
...     s: str
...     f: float
...     b: bool
>>>
>>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
'{"i":10,"s":"foo","f":100.0,"b":true}'
@dataclass_transform()
def deserialize( _cls: type[~T] | None = None, rename_all: str | None = None, reuse_instances_default: bool = True, convert_sets_default: bool = False, deserializer: Callable[[type[typing.Any], typing.Any], typing.Any] | None = None, tagging: serde.core.Tagging = Tagging(tag=None, content=None, kind=<Kind.External: 1>), type_check: serde.core.TypeCheck = TypeCheck(kind=<Kind.Strict: 3>), class_deserializer: ClassDeserializer | None = None, deny_unknown_fields: bool = False, **kwargs: Any) -> type[~T]:
198@dataclass_transform()
199def deserialize(
200    _cls: type[T] | None = None,
201    rename_all: str | None = None,
202    reuse_instances_default: bool = True,
203    convert_sets_default: bool = False,
204    deserializer: DeserializeFunc | None = None,
205    tagging: Tagging = DefaultTagging,
206    type_check: TypeCheck = strict,
207    class_deserializer: ClassDeserializer | None = None,
208    deny_unknown_fields: bool = False,
209    **kwargs: Any,
210) -> type[T]:
211    """
212    A dataclass with this decorator is deserializable from any of the data formats supported
213    by pyserde.
214
215    >>> from serde import deserialize
216    >>> from serde.json import from_json
217    >>>
218    >>> @deserialize
219    ... class Foo:
220    ...     i: int
221    ...     s: str
222    ...     f: float
223    ...     b: bool
224    >>>
225    >>> from_json(Foo, '{"i": 10, "s": "foo", "f": 100.0, "b": true}')
226    Foo(i=10, s='foo', f=100.0, b=True)
227    """
228
229    stack = []
230
231    def wrap(cls: type[T]) -> type[T]:
232        if cls in stack:
233            return cls
234        stack.append(cls)
235
236        tagging.check()
237
238        # If no `dataclass` found in the class, dataclassify it automatically.
239        if not is_dataclass(cls):
240            dataclass(cls)
241
242        if type_check.is_strict():
243            serde_beartype = beartype(conf=BeartypeConf(violation_type=SerdeError))
244            serde_beartype(cls)
245
246        g: dict[str, Any] = {}
247
248        # Create a scope storage used by serde.
249        # Each class should get own scope. Child classes can not share scope with parent class.
250        # That's why we need the "scope.cls is not cls" check.
251        scope: Scope | None = getattr(cls, SERDE_SCOPE, None)
252        if scope is None or scope.cls is not cls:
253            scope = Scope(cls, reuse_instances_default=reuse_instances_default)
254            setattr(cls, SERDE_SCOPE, scope)
255
256        class_deserializers: list[ClassDeserializer] = list(
257            itertools.chain(
258                GLOBAL_CLASS_DESERIALIZER, [class_deserializer] if class_deserializer else []
259            )
260        )
261
262        # Set some globals for all generated functions
263        g["cls"] = cls
264        g["serde_scope"] = scope
265        g["SerdeError"] = SerdeError
266        g["UserError"] = UserError
267        g["raise_unsupported_type"] = raise_unsupported_type
268        g["typename"] = typename
269        g["ensure"] = ensure
270        g["typing"] = typing
271        g["collections"] = collections
272        g["Literal"] = Literal
273        g["from_obj"] = from_obj
274        g["get_generic_arg"] = get_generic_arg
275        g["is_instance"] = is_instance
276        g["TypeCheck"] = TypeCheck
277        g["disabled"] = disabled
278        g["coerce_object"] = coerce_object
279        g["_exists_by_aliases"] = _exists_by_aliases
280        g["_get_by_aliases"] = _get_by_aliases
281        g["class_deserializers"] = class_deserializers
282        g["BeartypeCallHintParamViolation"] = BeartypeCallHintParamViolation
283        g["is_bearable"] = is_bearable
284        if deserializer:
285            g["serde_legacy_custom_class_deserializer"] = functools.partial(
286                serde_legacy_custom_class_deserializer, custom=deserializer
287            )
288
289        # Collect types used in the generated code.
290        for typ in iter_types(cls):
291            # When we encounter a dataclass not marked with deserialize, then also generate
292            # deserialize functions for it.
293            if is_dataclass_without_de(typ) and typ is not cls:
294                # We call deserialize and not wrap to make sure that we will use the default serde
295                # configuration for generating the deserialization function.
296                deserialize(typ)
297
298            # We don't want to add primitive class e.g "str" into the scope, but primitive
299            # compatible types such as IntEnum and a subclass of primitives are added,
300            # so that generated code can use those types.
301            if is_primitive(typ) and not is_enum(typ) and not is_primitive_subclass(typ):
302                continue
303
304            if is_generic(typ):
305                g[typename(typ)] = get_origin(typ)
306            else:
307                g[typename(typ)] = typ
308
309        # render all union functions
310        for union in iter_unions(cls):
311            union_args = type_args(union)
312            add_func(
313                scope,
314                union_func_name(UNION_DE_PREFIX, union_args),
315                render_union_func(cls, union_args, tagging),
316                g,
317            )
318
319        # render literal functions
320        for literal in iter_literals(cls):
321            literal_args = type_args(literal)
322            add_func(
323                scope, literal_func_name(literal_args), render_literal_func(cls, literal_args), g
324            )
325
326        # Collect default values and default factories used in the generated code.
327        for f in defields(cls):
328            assert f.name
329            if has_default(f):
330                scope.defaults[f.name] = f.default
331            elif has_default_factory(f):
332                scope.defaults[f.name] = f.default_factory
333            if f.deserializer:
334                g[f.deserializer.name] = f.deserializer
335
336        add_func(
337            scope,
338            FROM_ITER,
339            render_from_iter(cls, deserializer, type_check, class_deserializer=class_deserializer),
340            g,
341        )
342        add_func(
343            scope,
344            FROM_DICT,
345            render_from_dict(
346                cls,
347                rename_all,
348                deserializer,
349                type_check,
350                class_deserializer=class_deserializer,
351                deny_unknown_fields=deny_unknown_fields,
352            ),
353            g,
354        )
355
356        logger.debug(f"{typename(cls)}: {SERDE_SCOPE} {scope}")
357
358        stack.pop()
359        return cls
360
361    if _cls is None:
362        return wrap  # type: ignore
363
364    if _cls in GENERATION_STACK:
365        return _cls
366
367    GENERATION_STACK.append(_cls)
368    try:
369        return wrap(_cls)
370    finally:
371        GENERATION_STACK.pop()

A dataclass with this decorator is deserializable from any of the data formats supported by pyserde.

>>> from serde import deserialize
>>> from serde.json import from_json
>>>
>>> @deserialize
... class Foo:
...     i: int
...     s: str
...     f: float
...     b: bool
>>>
>>> from_json(Foo, '{"i": 10, "s": "foo", "f": 100.0, "b": true}')
Foo(i=10, s='foo', f=100.0, b=True)
def is_serializable(instance_or_class: Any) -> bool:
329def is_serializable(instance_or_class: Any) -> bool:
330    """
331    Test if an instance or class is serializable.
332
333    >>> @serialize
334    ... class Foo:
335    ...     pass
336
337    Testing `Foo` class object returns `True`.
338    >>> is_serializable(Foo)
339    True
340
341    Testing `Foo` object also returns `True`.
342    >>> is_serializable(Foo())
343    True
344    """
345    return hasattr(instance_or_class, SERDE_SCOPE)

Test if an instance or class is serializable.

>>> @serialize
... class Foo:
...     pass

Testing Foo class object returns True.

>>> is_serializable(Foo)
True

Testing Foo object also returns True.

>>> is_serializable(Foo())
True
def is_deserializable(instance_or_class: Any) -> bool:
374def is_deserializable(instance_or_class: Any) -> bool:
375    """
376    Test if an instance or class is deserializable.
377
378    >>> @deserialize
379    ... class Foo:
380    ...     pass
381    >>>
382    >>> is_deserializable(Foo)
383    True
384    """
385    return hasattr(instance_or_class, SERDE_SCOPE)

Test if an instance or class is deserializable.

>>> @deserialize
... class Foo:
...     pass
>>>
>>> is_deserializable(Foo)
True
def to_dict( o: Any, c: type[typing.Any] | None = None, reuse_instances: bool | None = None, convert_sets: bool | None = None, skip_none: bool = False) -> dict[typing.Any, typing.Any]:
476def to_dict(
477    o: Any,
478    c: type[Any] | None = None,
479    reuse_instances: bool | None = None,
480    convert_sets: bool | None = None,
481    skip_none: bool = False,
482) -> dict[Any, Any]:
483    """
484    Serialize object into python dictionary. This function ensures that the dataclass's fields are
485    accurately represented as key-value pairs in the resulting dictionary.
486
487    * `o`: Any pyserde object that you want to convert to `dict`
488    * `c`: Optional class argument
489    * `reuse_instances`: pyserde will pass instances (e.g. Path, datetime) directly to serializer
490    instead of converting them to serializable representation e.g. string. This behaviour allows
491    to delegate serializtation to underlying data format packages e.g. `pyyaml` and potentially
492    improve performance.
493    * `convert_sets`: This option controls how sets are handled during serialization and
494    deserialization. When `convert_sets` is set to True, pyserde will convert sets to lists during
495    serialization and back to sets during deserialization. This is useful for data formats that
496    do not natively support sets.
497    * `skip_none`: When set to True, any field in the class with a None value is excluded from the
498    serialized output. Defaults to False.
499
500    >>> from serde import serde
501    >>> @serde
502    ... class Foo:
503    ...     i: int
504    ...     s: str = 'foo'
505    ...     f: float = 100.0
506    ...     b: bool = True
507    >>>
508    >>> to_dict(Foo(i=10))
509    {'i': 10, 's': 'foo', 'f': 100.0, 'b': True}
510
511    You can serialize not only pyserde objects but also objects of any supported types. For example,
512    the following example serializes list of pyserde objects into dict.
513
514    >>> lst = [Foo(i=10), Foo(i=20)]
515    >>> to_dict(lst)
516    [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
517    """
518    return to_obj(  # type: ignore
519        o,
520        named=True,
521        c=c,
522        reuse_instances=reuse_instances,
523        convert_sets=convert_sets,
524        skip_none=skip_none,
525    )

Serialize object into python dictionary. This function ensures that the dataclass's fields are accurately represented as key-value pairs in the resulting dictionary.

  • o: Any pyserde object that you want to convert to dict
  • c: Optional class argument
  • reuse_instances: pyserde will pass instances (e.g. Path, datetime) directly to serializer instead of converting them to serializable representation e.g. string. This behaviour allows to delegate serializtation to underlying data format packages e.g. pyyaml and potentially improve performance.
  • convert_sets: This option controls how sets are handled during serialization and deserialization. When convert_sets is set to True, pyserde will convert sets to lists during serialization and back to sets during deserialization. This is useful for data formats that do not natively support sets.
  • skip_none: When set to True, any field in the class with a None value is excluded from the serialized output. Defaults to False.
>>> from serde import serde
>>> @serde
... class Foo:
...     i: int
...     s: str = 'foo'
...     f: float = 100.0
...     b: bool = True
>>>
>>> to_dict(Foo(i=10))
{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}

You can serialize not only pyserde objects but also objects of any supported types. For example, the following example serializes list of pyserde objects into dict.

>>> lst = [Foo(i=10), Foo(i=20)]
>>> to_dict(lst)
[{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
def from_dict( cls: Any, o: dict[str, typing.Any], reuse_instances: bool | None = None, deserialize_numbers: Callable[[str | int], float] | None = None) -> Any:
564def from_dict(
565    cls: Any,
566    o: dict[str, Any],
567    reuse_instances: bool | None = None,
568    deserialize_numbers: Callable[[str | int], float] | None = None,
569) -> Any:
570    """
571    Deserialize dictionary into object.
572
573    >>> @deserialize
574    ... class Foo:
575    ...     i: int
576    ...     s: str = 'foo'
577    ...     f: float = 100.0
578    ...     b: bool = True
579    >>>
580    >>> from_dict(Foo, {'i': 10, 's': 'foo', 'f': 100.0, 'b': True})
581    Foo(i=10, s='foo', f=100.0, b=True)
582
583    You can pass any type supported by pyserde. For example,
584
585    * `deserialize_numbers`: Optional callable to coerce numeric input to floats when the target
586      type is float (e.g. accept ints or numeric strings supplied by a parser).
587
588    >>> lst = [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True},
589    ...        {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
590    >>> from_dict(list[Foo], lst)
591    [Foo(i=10, s='foo', f=100.0, b=True), Foo(i=20, s='foo', f=100.0, b=True)]
592    """
593    return from_obj(
594        cls,
595        o,
596        named=True,
597        reuse_instances=reuse_instances,
598        deserialize_numbers=deserialize_numbers,
599    )

Deserialize dictionary into object.

>>> @deserialize
... class Foo:
...     i: int
...     s: str = 'foo'
...     f: float = 100.0
...     b: bool = True
>>>
>>> from_dict(Foo, {'i': 10, 's': 'foo', 'f': 100.0, 'b': True})
Foo(i=10, s='foo', f=100.0, b=True)

You can pass any type supported by pyserde. For example,

  • deserialize_numbers: Optional callable to coerce numeric input to floats when the target type is float (e.g. accept ints or numeric strings supplied by a parser).
>>> lst = [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True},
...        {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
>>> from_dict(list[Foo], lst)
[Foo(i=10, s='foo', f=100.0, b=True), Foo(i=20, s='foo', f=100.0, b=True)]
def to_tuple( o: Any, c: type[typing.Any] | None = None, reuse_instances: bool | None = None, convert_sets: bool | None = None, skip_none: bool = False) -> tuple[typing.Any, ...]:
433def to_tuple(
434    o: Any,
435    c: type[Any] | None = None,
436    reuse_instances: bool | None = None,
437    convert_sets: bool | None = None,
438    skip_none: bool = False,
439) -> tuple[Any, ...]:
440    """
441    Serialize object into tuple.
442
443    >>> @serialize
444    ... class Foo:
445    ...     i: int
446    ...     s: str = 'foo'
447    ...     f: float = 100.0
448    ...     b: bool = True
449    >>>
450    >>> to_tuple(Foo(i=10))
451    (10, 'foo', 100.0, True)
452
453    You can pass any type supported by pyserde. For example,
454
455    >>> lst = [Foo(i=10), Foo(i=20)]
456    >>> to_tuple(lst)
457    [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
458    """
459    return to_obj(  # type: ignore
460        o,
461        named=False,
462        c=c,
463        reuse_instances=reuse_instances,
464        convert_sets=convert_sets,
465        skip_none=skip_none,
466    )

Serialize object into tuple.

>>> @serialize
... class Foo:
...     i: int
...     s: str = 'foo'
...     f: float = 100.0
...     b: bool = True
>>>
>>> to_tuple(Foo(i=10))
(10, 'foo', 100.0, True)

You can pass any type supported by pyserde. For example,

>>> lst = [Foo(i=10), Foo(i=20)]
>>> to_tuple(lst)
[(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
def from_tuple( cls: Any, o: Any, reuse_instances: bool | None = None, deserialize_numbers: Callable[[str | int], float] | None = None) -> Any:
620def from_tuple(
621    cls: Any,
622    o: Any,
623    reuse_instances: bool | None = None,
624    deserialize_numbers: Callable[[str | int], float] | None = None,
625) -> Any:
626    """
627    Deserialize tuple into object.
628
629    >>> @deserialize
630    ... class Foo:
631    ...     i: int
632    ...     s: str = 'foo'
633    ...     f: float = 100.0
634    ...     b: bool = True
635    >>>
636    >>> from_tuple(Foo, (10, 'foo', 100.0, True))
637    Foo(i=10, s='foo', f=100.0, b=True)
638
639    You can pass any type supported by pyserde. For example,
640
641    * `deserialize_numbers`: Optional callable to coerce numeric input to floats when the target
642      type is float (e.g. accept ints or numeric strings supplied by a parser).
643
644    >>> lst = [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
645    >>> from_tuple(list[Foo], lst)
646    [Foo(i=10, s='foo', f=100.0, b=True), Foo(i=20, s='foo', f=100.0, b=True)]
647    """
648    return from_obj(
649        cls,
650        o,
651        named=False,
652        reuse_instances=reuse_instances,
653        deserialize_numbers=deserialize_numbers,
654    )

Deserialize tuple into object.

>>> @deserialize
... class Foo:
...     i: int
...     s: str = 'foo'
...     f: float = 100.0
...     b: bool = True
>>>
>>> from_tuple(Foo, (10, 'foo', 100.0, True))
Foo(i=10, s='foo', f=100.0, b=True)

You can pass any type supported by pyserde. For example,

  • deserialize_numbers: Optional callable to coerce numeric input to floats when the target type is float (e.g. accept ints or numeric strings supplied by a parser).
>>> lst = [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
>>> from_tuple(list[Foo], lst)
[Foo(i=10, s='foo', f=100.0, b=True), Foo(i=20, s='foo', f=100.0, b=True)]
class SerdeError(builtins.Exception):
93class SerdeError(Exception):
94    """
95    Serde error class.
96    """

Serde error class.

class SerdeSkip(builtins.Exception):
108class SerdeSkip(Exception):
109    """
110    Skip a field in custom (de)serializer.
111    """

Skip a field in custom (de)serializer.

def AdjacentTagging( tag: str, content: str, cls: Optional[~T] = None) -> Union[serde.core.Tagging, serde.compat._WithTagging[~T]]:
871def AdjacentTagging(tag: str, content: str, cls: T | None = None) -> Tagging | _WithTagging[T]:
872    tagging = Tagging(tag, content, kind=Tagging.Kind.Adjacent)
873    if cls:
874        return tagging(cls)
875    else:
876        return tagging
ExternalTagging = Tagging(tag=None, content=None, kind=<Kind.External: 1>)
def InternalTagging( tag: str, cls: Optional[~T] = None) -> Union[serde.core.Tagging, serde.compat._WithTagging[~T]]:
855def InternalTagging(tag: str, cls: T | None = None) -> Tagging | _WithTagging[T]:
856    tagging = Tagging(tag, kind=Tagging.Kind.Internal)
857    if cls:
858        return tagging(cls)
859    else:
860        return tagging
Untagged = Tagging(tag=None, content=None, kind=<Kind.Untagged: 4>)
disabled = TypeCheck(kind=<Kind.Disabled: 1>)
strict = TypeCheck(kind=<Kind.Strict: 3>)
coerce = TypeCheck(kind=<Kind.Coerce: 2>)
def field( *args: Any, rename: str | None = None, alias: list[str] | None = None, skip: bool | None = None, skip_if: Callable[[typing.Any], typing.Any] | None = None, skip_if_false: bool | None = None, skip_if_default: bool | None = None, serializer: Callable[..., typing.Any] | None = None, deserializer: Callable[..., typing.Any] | None = None, flatten: serde.core.FlattenOpts | bool | None = None, metadata: dict[str, typing.Any] | None = None, **kwargs: Any) -> Any:
533def field(
534    *args: Any,
535    rename: str | None = None,
536    alias: list[str] | None = None,
537    skip: bool | None = None,
538    skip_if: Callable[[Any], Any] | None = None,
539    skip_if_false: bool | None = None,
540    skip_if_default: bool | None = None,
541    serializer: Callable[..., Any] | None = None,
542    deserializer: Callable[..., Any] | None = None,
543    flatten: FlattenOpts | bool | None = None,
544    metadata: dict[str, Any] | None = None,
545    **kwargs: Any,
546) -> Any:
547    """
548    Declare a field with parameters.
549    """
550    if not metadata:
551        metadata = {}
552
553    if rename is not None:
554        metadata["serde_rename"] = rename
555    if alias is not None:
556        metadata["serde_alias"] = alias
557    if skip is not None:
558        metadata["serde_skip"] = skip
559    if skip_if is not None:
560        metadata["serde_skip_if"] = skip_if
561    if skip_if_false is not None:
562        metadata["serde_skip_if_false"] = skip_if_false
563    if skip_if_default is not None:
564        metadata["serde_skip_if_default"] = skip_if_default
565    if serializer:
566        metadata["serde_serializer"] = serializer
567    if deserializer:
568        metadata["serde_deserializer"] = deserializer
569    if flatten is True:
570        metadata["serde_flatten"] = FlattenOpts()
571    elif flatten:
572        metadata["serde_flatten"] = flatten
573
574    return dataclasses.field(*args, metadata=metadata, **kwargs)

Declare a field with parameters.

def default_deserializer(_cls: type[typing.Any], obj: Any) -> Any:
139def default_deserializer(_cls: type[Any], obj: Any) -> Any:
140    """
141    Marker function to tell serde to use the default deserializer. It's used when custom
142    deserializer is specified at the class but you want to override a field with the default
143    deserializer.
144    """

Marker function to tell serde to use the default deserializer. It's used when custom deserializer is specified at the class but you want to override a field with the default deserializer.

def asdict(v: Any) -> dict[typing.Any, typing.Any]:
469def asdict(v: Any) -> dict[Any, Any]:
470    """
471    Serialize object into dictionary.
472    """
473    return to_dict(v, reuse_instances=False, convert_sets=False)

Serialize object into dictionary.

def astuple(v: Any) -> tuple[typing.Any, ...]:
426def astuple(v: Any) -> tuple[Any, ...]:
427    """
428    Serialize object into tuple.
429    """
430    return to_tuple(v, reuse_instances=False, convert_sets=False)

Serialize object into tuple.

def default_serializer(_cls: type[typing.Any], obj: Any) -> Any:
121def default_serializer(_cls: type[Any], obj: Any) -> Any:
122    """
123    Marker function to tell serde to use the default serializer. It's used when custom serializer
124    is specified at the class but you want to override a field with the default serializer.
125    """

Marker function to tell serde to use the default serializer. It's used when custom serializer is specified at the class but you want to override a field with the default serializer.

def init(debug: bool = False) -> None:
94def init(debug: bool = False) -> None:
95    SETTINGS["debug"] = debug
logger = <Logger serde (WARNING)>
class ClassSerializer(typing.Protocol):
1018class ClassSerializer(Protocol):
1019    """
1020    Interface for custom class serializer.
1021
1022    This protocol is intended to be used for custom class serializer.
1023
1024    >>> from datetime import datetime
1025    >>> from serde import serde
1026    >>> from plum import dispatch
1027    >>> class MySerializer(ClassSerializer):
1028    ...     @dispatch
1029    ...     def serialize(self, value: datetime) -> str:
1030    ...         return value.strftime("%d/%m/%y")
1031    """
1032
1033    def serialize(self, value: Any) -> Any:
1034        pass

Interface for custom class serializer.

This protocol is intended to be used for custom class serializer.

>>> from datetime import datetime
>>> from serde import serde
>>> from plum import dispatch
>>> class MySerializer(ClassSerializer):
...     @dispatch
...     def serialize(self, value: datetime) -> str:
...         return value.strftime("%d/%m/%y")
ClassSerializer(*args, **kwargs)
1771def _no_init_or_replace_init(self, *args, **kwargs):
1772    cls = type(self)
1773
1774    if cls._is_protocol:
1775        raise TypeError('Protocols cannot be instantiated')
1776
1777    # Already using a custom `__init__`. No need to calculate correct
1778    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1779    if cls.__init__ is not _no_init_or_replace_init:
1780        return
1781
1782    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1783    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1784    # searches for a proper new `__init__` in the MRO. The new `__init__`
1785    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1786    # instantiation of the protocol subclass will thus use the new
1787    # `__init__` and no longer call `_no_init_or_replace_init`.
1788    for base in cls.__mro__:
1789        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1790        if init is not _no_init_or_replace_init:
1791            cls.__init__ = init
1792            break
1793    else:
1794        # should not happen
1795        cls.__init__ = object.__init__
1796
1797    cls.__init__(self, *args, **kwargs)
def serialize(self, value: Any) -> Any:
1033    def serialize(self, value: Any) -> Any:
1034        pass
class ClassDeserializer(typing.Protocol):
1037class ClassDeserializer(Protocol):
1038    """
1039    Interface for custom class deserializer.
1040
1041    This protocol is intended to be used for custom class deserializer.
1042
1043    >>> from datetime import datetime
1044    >>> from serde import serde
1045    >>> from plum import dispatch
1046    >>> class MyDeserializer(ClassDeserializer):
1047    ...     @dispatch
1048    ...     def deserialize(self, cls: type[datetime], value: Any) -> datetime:
1049    ...         return datetime.strptime(value, "%d/%m/%y")
1050    """
1051
1052    def deserialize(self, cls: Any, value: Any) -> Any:
1053        pass

Interface for custom class deserializer.

This protocol is intended to be used for custom class deserializer.

>>> from datetime import datetime
>>> from serde import serde
>>> from plum import dispatch
>>> class MyDeserializer(ClassDeserializer):
...     @dispatch
...     def deserialize(self, cls: type[datetime], value: Any) -> datetime:
...         return datetime.strptime(value, "%d/%m/%y")
ClassDeserializer(*args, **kwargs)
1771def _no_init_or_replace_init(self, *args, **kwargs):
1772    cls = type(self)
1773
1774    if cls._is_protocol:
1775        raise TypeError('Protocols cannot be instantiated')
1776
1777    # Already using a custom `__init__`. No need to calculate correct
1778    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1779    if cls.__init__ is not _no_init_or_replace_init:
1780        return
1781
1782    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1783    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1784    # searches for a proper new `__init__` in the MRO. The new `__init__`
1785    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1786    # instantiation of the protocol subclass will thus use the new
1787    # `__init__` and no longer call `_no_init_or_replace_init`.
1788    for base in cls.__mro__:
1789        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1790        if init is not _no_init_or_replace_init:
1791            cls.__init__ = init
1792            break
1793    else:
1794        # should not happen
1795        cls.__init__ = object.__init__
1796
1797    cls.__init__(self, *args, **kwargs)
def deserialize(self, cls: Any, value: Any) -> Any:
1052    def deserialize(self, cls: Any, value: Any) -> Any:
1053        pass
def add_serializer(serializer: ClassSerializer) -> None:
1061def add_serializer(serializer: ClassSerializer) -> None:
1062    """
1063    Register custom global serializer.
1064    """
1065    GLOBAL_CLASS_SERIALIZER.append(serializer)

Register custom global serializer.

def add_deserializer(deserializer: ClassDeserializer) -> None:
1068def add_deserializer(deserializer: ClassDeserializer) -> None:
1069    """
1070    Register custom global deserializer.
1071    """
1072    GLOBAL_CLASS_DESERIALIZER.append(deserializer)

Register custom global deserializer.