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 Optional, overload, Any, Type
 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: Optional[str] = None,
118    reuse_instances_default: bool = True,
119    convert_sets_default: bool = False,
120    serializer: Optional[SerializeFunc] = None,
121    deserializer: Optional[DeserializeFunc] = None,
122    tagging: Tagging = DefaultTagging,
123    type_check: TypeCheck = strict,
124    serialize_class_var: bool = False,
125    class_serializer: Optional[ClassSerializer] = None,
126    class_deserializer: Optional[ClassDeserializer] = None,
127    deny_unknown_fields: bool = False,
128) -> Type[T]: ...
129
130
131@overload
132def serde(
133    _cls: Any = None,
134    rename_all: Optional[str] = None,
135    reuse_instances_default: bool = True,
136    convert_sets_default: bool = False,
137    serializer: Optional[SerializeFunc] = None,
138    deserializer: Optional[DeserializeFunc] = None,
139    tagging: Tagging = DefaultTagging,
140    type_check: TypeCheck = strict,
141    serialize_class_var: bool = False,
142    class_serializer: Optional[ClassSerializer] = None,
143    class_deserializer: Optional[ClassDeserializer] = 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: Optional[str] = None,
152    reuse_instances_default: bool = True,
153    convert_sets_default: bool = False,
154    serializer: Optional[SerializeFunc] = None,
155    deserializer: Optional[DeserializeFunc] = None,
156    tagging: Tagging = DefaultTagging,
157    type_check: TypeCheck = strict,
158    serialize_class_var: bool = False,
159    class_serializer: Optional[ClassSerializer] = None,
160    class_deserializer: Optional[ClassDeserializer] = 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: Optional[str] = None, reuse_instances_default: bool = True, convert_sets_default: bool = False, serializer: Optional[Callable[[type[Any], Any], Any]] = None, deserializer: Optional[Callable[[type[Any], Any], Any]] = 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: Optional[ClassSerializer] = None, class_deserializer: Optional[ClassDeserializer] = None, deny_unknown_fields: bool = False) -> Any:
149@dataclass_transform(field_specifiers=(field,))
150def serde(
151    _cls: Any = None,
152    rename_all: Optional[str] = None,
153    reuse_instances_default: bool = True,
154    convert_sets_default: bool = False,
155    serializer: Optional[SerializeFunc] = None,
156    deserializer: Optional[DeserializeFunc] = None,
157    tagging: Tagging = DefaultTagging,
158    type_check: TypeCheck = strict,
159    serialize_class_var: bool = False,
160    class_serializer: Optional[ClassSerializer] = None,
161    class_deserializer: Optional[ClassDeserializer] = 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: Optional[type[~T]] = None, rename_all: Optional[str] = None, reuse_instances_default: bool = False, convert_sets_default: bool = False, serializer: Optional[Callable[[type[Any], Any], Any]] = 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: Optional[ClassSerializer] = None, **kwargs: Any) -> type[~T]:
182@dataclass_transform()
183def serialize(
184    _cls: Optional[type[T]] = None,
185    rename_all: Optional[str] = None,
186    reuse_instances_default: bool = False,
187    convert_sets_default: bool = False,
188    serializer: Optional[SerializeFunc] = None,
189    tagging: Tagging = DefaultTagging,
190    type_check: TypeCheck = strict,
191    serialize_class_var: bool = False,
192    class_serializer: Optional[ClassSerializer] = None,
193    **kwargs: Any,
194) -> type[T]:
195    """
196    A dataclass with this decorator is serializable into any of the data formats
197    supported by pyserde.
198
199    >>> from datetime import datetime
200    >>> from serde import serialize
201    >>> from serde.json import to_json
202    >>>
203    >>> @serialize
204    ... class Foo:
205    ...     i: int
206    ...     s: str
207    ...     f: float
208    ...     b: bool
209    >>>
210    >>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
211    '{"i":10,"s":"foo","f":100.0,"b":true}'
212    """
213
214    def wrap(cls: type[T]) -> type[T]:
215        tagging.check()
216
217        # If no `dataclass` found in the class, dataclassify it automatically.
218        if not is_dataclass(cls):
219            dataclass(cls)
220
221        if type_check.is_strict():
222            serde_beartype = beartype(conf=BeartypeConf(violation_type=SerdeError))
223            serde_beartype(cls)
224
225        g: dict[str, Any] = {}
226
227        # Create a scope storage used by serde.
228        # Each class should get own scope. Child classes can not share scope with parent class.
229        # That's why we need the "scope.cls is not cls" check.
230        scope: Optional[Scope] = getattr(cls, SERDE_SCOPE, None)
231        if scope is None or scope.cls is not cls:
232            scope = Scope(
233                cls,
234                reuse_instances_default=reuse_instances_default,
235                convert_sets_default=convert_sets_default,
236            )
237            setattr(cls, SERDE_SCOPE, scope)
238
239        class_serializers: list[ClassSerializer] = list(
240            itertools.chain(GLOBAL_CLASS_SERIALIZER, [class_serializer] if class_serializer else [])
241        )
242
243        # Set some globals for all generated functions
244        g["cls"] = cls
245        g["copy"] = copy
246        g["serde_scope"] = scope
247        g["SerdeError"] = SerdeError
248        g["raise_unsupported_type"] = raise_unsupported_type
249        g["enum_value"] = enum_value
250        g["is_dataclass"] = is_dataclass
251        g["typename"] = typename  # used in union functions
252        g["is_instance"] = is_instance  # used in union functions
253        g["to_obj"] = to_obj
254        g["typing"] = typing
255        g["Literal"] = Literal
256        g["TypeCheck"] = TypeCheck
257        g["disabled"] = disabled
258        g["coerce_object"] = coerce_object
259        g["class_serializers"] = class_serializers
260        if serializer:
261            g["serde_legacy_custom_class_serializer"] = functools.partial(
262                serde_legacy_custom_class_serializer, custom=serializer
263            )
264
265        # Collect types used in the generated code.
266        for typ in iter_types(cls):
267            # When we encounter a dataclass not marked with serialize, then also generate serialize
268            # functions for it.
269            if is_dataclass_without_se(typ) and typ is not cls:
270                # We call serialize and not wrap to make sure that we will use the default serde
271                # configuration for generating the serialization function.
272                serialize(typ)
273
274            if is_primitive(typ) and not is_enum(typ):
275                continue
276            g[typename(typ)] = typ
277
278        # render all union functions
279        for union in iter_unions(cls):
280            union_args = list(type_args(union))
281            union_key = union_func_name(UNION_SE_PREFIX, union_args)
282            add_func(scope, union_key, render_union_func(cls, union_args, tagging), g)
283            scope.union_se_args[union_key] = union_args
284
285        for f in sefields(cls, serialize_class_var):
286            if f.skip_if:
287                g[f.skip_if.name] = f.skip_if
288            if f.serializer:
289                g[f.serializer.name] = f.serializer
290
291        add_func(
292            scope,
293            TO_ITER,
294            render_to_tuple(cls, serializer, type_check, serialize_class_var, class_serializer),
295            g,
296        )
297        add_func(
298            scope,
299            TO_DICT,
300            render_to_dict(
301                cls, rename_all, serializer, type_check, serialize_class_var, class_serializer
302            ),
303            g,
304        )
305
306        logger.debug(f"{typename(cls)}: {SERDE_SCOPE} {scope}")
307
308        return cls
309
310    if _cls is None:
311        return wrap  # type: ignore
312
313    if _cls in GENERATION_STACK:
314        return _cls
315
316    GENERATION_STACK.append(_cls)
317    try:
318        return wrap(_cls)
319    finally:
320        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: Optional[type[~T]] = None, rename_all: Optional[str] = None, reuse_instances_default: bool = True, convert_sets_default: bool = False, deserializer: Optional[Callable[[type[Any], Any], Any]] = 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: Optional[ClassDeserializer] = None, deny_unknown_fields: bool = False, **kwargs: Any) -> type[~T]:
200@dataclass_transform()
201def deserialize(
202    _cls: Optional[type[T]] = None,
203    rename_all: Optional[str] = None,
204    reuse_instances_default: bool = True,
205    convert_sets_default: bool = False,
206    deserializer: Optional[DeserializeFunc] = None,
207    tagging: Tagging = DefaultTagging,
208    type_check: TypeCheck = strict,
209    class_deserializer: Optional[ClassDeserializer] = None,
210    deny_unknown_fields: bool = False,
211    **kwargs: Any,
212) -> type[T]:
213    """
214    A dataclass with this decorator is deserializable from any of the data formats supported
215    by pyserde.
216
217    >>> from serde import deserialize
218    >>> from serde.json import from_json
219    >>>
220    >>> @deserialize
221    ... class Foo:
222    ...     i: int
223    ...     s: str
224    ...     f: float
225    ...     b: bool
226    >>>
227    >>> from_json(Foo, '{"i": 10, "s": "foo", "f": 100.0, "b": true}')
228    Foo(i=10, s='foo', f=100.0, b=True)
229    """
230
231    stack = []
232
233    def wrap(cls: type[T]) -> type[T]:
234        if cls in stack:
235            return cls
236        stack.append(cls)
237
238        tagging.check()
239
240        # If no `dataclass` found in the class, dataclassify it automatically.
241        if not is_dataclass(cls):
242            dataclass(cls)
243
244        if type_check.is_strict():
245            serde_beartype = beartype(conf=BeartypeConf(violation_type=SerdeError))
246            serde_beartype(cls)
247
248        g: dict[str, Any] = {}
249
250        # Create a scope storage used by serde.
251        # Each class should get own scope. Child classes can not share scope with parent class.
252        # That's why we need the "scope.cls is not cls" check.
253        scope: Optional[Scope] = getattr(cls, SERDE_SCOPE, None)
254        if scope is None or scope.cls is not cls:
255            scope = Scope(cls, reuse_instances_default=reuse_instances_default)
256            setattr(cls, SERDE_SCOPE, scope)
257
258        class_deserializers: list[ClassDeserializer] = list(
259            itertools.chain(
260                GLOBAL_CLASS_DESERIALIZER, [class_deserializer] if class_deserializer else []
261            )
262        )
263
264        # Set some globals for all generated functions
265        g["cls"] = cls
266        g["serde_scope"] = scope
267        g["SerdeError"] = SerdeError
268        g["UserError"] = UserError
269        g["raise_unsupported_type"] = raise_unsupported_type
270        g["typename"] = typename
271        g["ensure"] = ensure
272        g["typing"] = typing
273        g["collections"] = collections
274        g["Literal"] = Literal
275        g["from_obj"] = from_obj
276        g["get_generic_arg"] = get_generic_arg
277        g["is_instance"] = is_instance
278        g["TypeCheck"] = TypeCheck
279        g["disabled"] = disabled
280        g["coerce_object"] = coerce_object
281        g["_exists_by_aliases"] = _exists_by_aliases
282        g["_get_by_aliases"] = _get_by_aliases
283        g["class_deserializers"] = class_deserializers
284        g["BeartypeCallHintParamViolation"] = BeartypeCallHintParamViolation
285        g["is_bearable"] = is_bearable
286        if deserializer:
287            g["serde_legacy_custom_class_deserializer"] = functools.partial(
288                serde_legacy_custom_class_deserializer, custom=deserializer
289            )
290
291        # Collect types used in the generated code.
292        for typ in iter_types(cls):
293            # When we encounter a dataclass not marked with deserialize, then also generate
294            # deserialize functions for it.
295            if is_dataclass_without_de(typ) and typ is not cls:
296                # We call deserialize and not wrap to make sure that we will use the default serde
297                # configuration for generating the deserialization function.
298                deserialize(typ)
299
300            # We don't want to add primitive class e.g "str" into the scope, but primitive
301            # compatible types such as IntEnum and a subclass of primitives are added,
302            # so that generated code can use those types.
303            if is_primitive(typ) and not is_enum(typ) and not is_primitive_subclass(typ):
304                continue
305
306            if is_generic(typ):
307                g[typename(typ)] = get_origin(typ)
308            else:
309                g[typename(typ)] = typ
310
311        # render all union functions
312        for union in iter_unions(cls):
313            union_args = type_args(union)
314            add_func(
315                scope,
316                union_func_name(UNION_DE_PREFIX, union_args),
317                render_union_func(cls, union_args, tagging),
318                g,
319            )
320
321        # render literal functions
322        for literal in iter_literals(cls):
323            literal_args = type_args(literal)
324            add_func(
325                scope, literal_func_name(literal_args), render_literal_func(cls, literal_args), g
326            )
327
328        # Collect default values and default factories used in the generated code.
329        for f in defields(cls):
330            assert f.name
331            if has_default(f):
332                scope.defaults[f.name] = f.default
333            elif has_default_factory(f):
334                scope.defaults[f.name] = f.default_factory
335            if f.deserializer:
336                g[f.deserializer.name] = f.deserializer
337
338        add_func(
339            scope,
340            FROM_ITER,
341            render_from_iter(cls, deserializer, type_check, class_deserializer=class_deserializer),
342            g,
343        )
344        add_func(
345            scope,
346            FROM_DICT,
347            render_from_dict(
348                cls,
349                rename_all,
350                deserializer,
351                type_check,
352                class_deserializer=class_deserializer,
353                deny_unknown_fields=deny_unknown_fields,
354            ),
355            g,
356        )
357
358        logger.debug(f"{typename(cls)}: {SERDE_SCOPE} {scope}")
359
360        stack.pop()
361        return cls
362
363    if _cls is None:
364        return wrap  # type: ignore
365
366    if _cls in GENERATION_STACK:
367        return _cls
368
369    GENERATION_STACK.append(_cls)
370    try:
371        return wrap(_cls)
372    finally:
373        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:
323def is_serializable(instance_or_class: Any) -> bool:
324    """
325    Test if an instance or class is serializable.
326
327    >>> @serialize
328    ... class Foo:
329    ...     pass
330
331    Testing `Foo` class object returns `True`.
332    >>> is_serializable(Foo)
333    True
334
335    Testing `Foo` object also returns `True`.
336    >>> is_serializable(Foo())
337    True
338    """
339    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:
376def is_deserializable(instance_or_class: Any) -> bool:
377    """
378    Test if an instance or class is deserializable.
379
380    >>> @deserialize
381    ... class Foo:
382    ...     pass
383    >>>
384    >>> is_deserializable(Foo)
385    True
386    """
387    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: Optional[type[Any]] = None, reuse_instances: Optional[bool] = None, convert_sets: Optional[bool] = None, skip_none: bool = False) -> dict[typing.Any, typing.Any]:
470def to_dict(
471    o: Any,
472    c: Optional[type[Any]] = None,
473    reuse_instances: Optional[bool] = None,
474    convert_sets: Optional[bool] = None,
475    skip_none: bool = False,
476) -> dict[Any, Any]:
477    """
478    Serialize object into python dictionary. This function ensures that the dataclass's fields are
479    accurately represented as key-value pairs in the resulting dictionary.
480
481    * `o`: Any pyserde object that you want to convert to `dict`
482    * `c`: Optional class argument
483    * `reuse_instances`: pyserde will pass instances (e.g. Path, datetime) directly to serializer
484    instead of converting them to serializable representation e.g. string. This behaviour allows
485    to delegate serializtation to underlying data format packages e.g. `pyyaml` and potentially
486    improve performance.
487    * `convert_sets`: This option controls how sets are handled during serialization and
488    deserialization. When `convert_sets` is set to True, pyserde will convert sets to lists during
489    serialization and back to sets during deserialization. This is useful for data formats that
490    do not natively support sets.
491    * `skip_none`: When set to True, any field in the class with a None value is excluded from the
492    serialized output. Defaults to False.
493
494    >>> from serde import serde
495    >>> @serde
496    ... class Foo:
497    ...     i: int
498    ...     s: str = 'foo'
499    ...     f: float = 100.0
500    ...     b: bool = True
501    >>>
502    >>> to_dict(Foo(i=10))
503    {'i': 10, 's': 'foo', 'f': 100.0, 'b': True}
504
505    You can serialize not only pyserde objects but also objects of any supported types. For example,
506    the following example serializes list of pyserde objects into dict.
507
508    >>> lst = [Foo(i=10), Foo(i=20)]
509    >>> to_dict(lst)
510    [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
511    """
512    return to_obj(  # type: ignore
513        o,
514        named=True,
515        c=c,
516        reuse_instances=reuse_instances,
517        convert_sets=convert_sets,
518        skip_none=skip_none,
519    )

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: Optional[bool] = None) -> Any:
535def from_dict(cls: Any, o: dict[str, Any], reuse_instances: Optional[bool] = None) -> Any:
536    """
537    Deserialize dictionary into object.
538
539    >>> @deserialize
540    ... class Foo:
541    ...     i: int
542    ...     s: str = 'foo'
543    ...     f: float = 100.0
544    ...     b: bool = True
545    >>>
546    >>> from_dict(Foo, {'i': 10, 's': 'foo', 'f': 100.0, 'b': True})
547    Foo(i=10, s='foo', f=100.0, b=True)
548
549    You can pass any type supported by pyserde. For example,
550
551    >>> lst = [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True},
552    ...        {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
553    >>> from_dict(list[Foo], lst)
554    [Foo(i=10, s='foo', f=100.0, b=True), Foo(i=20, s='foo', f=100.0, b=True)]
555    """
556    return from_obj(cls, o, named=True, reuse_instances=reuse_instances)

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,

>>> 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: Optional[type[Any]] = None, reuse_instances: Optional[bool] = None, convert_sets: Optional[bool] = None, skip_none: bool = False) -> tuple[typing.Any, ...]:
427def to_tuple(
428    o: Any,
429    c: Optional[type[Any]] = None,
430    reuse_instances: Optional[bool] = None,
431    convert_sets: Optional[bool] = None,
432    skip_none: bool = False,
433) -> tuple[Any, ...]:
434    """
435    Serialize object into tuple.
436
437    >>> @serialize
438    ... class Foo:
439    ...     i: int
440    ...     s: str = 'foo'
441    ...     f: float = 100.0
442    ...     b: bool = True
443    >>>
444    >>> to_tuple(Foo(i=10))
445    (10, 'foo', 100.0, True)
446
447    You can pass any type supported by pyserde. For example,
448
449    >>> lst = [Foo(i=10), Foo(i=20)]
450    >>> to_tuple(lst)
451    [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
452    """
453    return to_obj(  # type: ignore
454        o,
455        named=False,
456        c=c,
457        reuse_instances=reuse_instances,
458        convert_sets=convert_sets,
459        skip_none=skip_none,
460    )

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: Optional[bool] = None) -> Any:
567def from_tuple(cls: Any, o: Any, reuse_instances: Optional[bool] = None) -> Any:
568    """
569    Deserialize tuple into object.
570
571    >>> @deserialize
572    ... class Foo:
573    ...     i: int
574    ...     s: str = 'foo'
575    ...     f: float = 100.0
576    ...     b: bool = True
577    >>>
578    >>> from_tuple(Foo, (10, 'foo', 100.0, True))
579    Foo(i=10, s='foo', f=100.0, b=True)
580
581    You can pass any type supported by pyserde. For example,
582
583    >>> lst = [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
584    >>> from_tuple(list[Foo], lst)
585    [Foo(i=10, s='foo', f=100.0, b=True), Foo(i=20, s='foo', f=100.0, b=True)]
586    """
587    return from_obj(cls, o, named=False, reuse_instances=reuse_instances)

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,

>>> 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):
83class SerdeError(Exception):
84    """
85    Serde error class.
86    """

Serde error class.

class SerdeSkip(builtins.Exception):
 98class SerdeSkip(Exception):
 99    """
100    Skip a field in custom (de)serializer.
101    """

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]]:
849def AdjacentTagging(
850    tag: str, content: str, cls: Optional[T] = None
851) -> Union[Tagging, _WithTagging[T]]:
852    tagging = Tagging(tag, content, kind=Tagging.Kind.Adjacent)
853    if cls:
854        return tagging(cls)
855    else:
856        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]]:
833def InternalTagging(tag: str, cls: Optional[T] = None) -> Union[Tagging, _WithTagging[T]]:
834    tagging = Tagging(tag, kind=Tagging.Kind.Internal)
835    if cls:
836        return tagging(cls)
837    else:
838        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: Optional[str] = None, alias: Optional[list[str]] = None, skip: Optional[bool] = None, skip_if: Optional[Callable[[Any], Any]] = None, skip_if_false: Optional[bool] = None, skip_if_default: Optional[bool] = None, serializer: Optional[Callable[..., Any]] = None, deserializer: Optional[Callable[..., Any]] = None, flatten: Union[serde.core.FlattenOpts, bool, NoneType] = None, metadata: Optional[dict[str, Any]] = None, **kwargs: Any) -> Any:
513def field(
514    *args: Any,
515    rename: Optional[str] = None,
516    alias: Optional[list[str]] = None,
517    skip: Optional[bool] = None,
518    skip_if: Optional[Callable[[Any], Any]] = None,
519    skip_if_false: Optional[bool] = None,
520    skip_if_default: Optional[bool] = None,
521    serializer: Optional[Callable[..., Any]] = None,
522    deserializer: Optional[Callable[..., Any]] = None,
523    flatten: Optional[Union[FlattenOpts, bool]] = None,
524    metadata: Optional[dict[str, Any]] = None,
525    **kwargs: Any,
526) -> Any:
527    """
528    Declare a field with parameters.
529    """
530    if not metadata:
531        metadata = {}
532
533    if rename is not None:
534        metadata["serde_rename"] = rename
535    if alias is not None:
536        metadata["serde_alias"] = alias
537    if skip is not None:
538        metadata["serde_skip"] = skip
539    if skip_if is not None:
540        metadata["serde_skip_if"] = skip_if
541    if skip_if_false is not None:
542        metadata["serde_skip_if_false"] = skip_if_false
543    if skip_if_default is not None:
544        metadata["serde_skip_if_default"] = skip_if_default
545    if serializer:
546        metadata["serde_serializer"] = serializer
547    if deserializer:
548        metadata["serde_deserializer"] = deserializer
549    if flatten is True:
550        metadata["serde_flatten"] = FlattenOpts()
551    elif flatten:
552        metadata["serde_flatten"] = flatten
553
554    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]:
463def asdict(v: Any) -> dict[Any, Any]:
464    """
465    Serialize object into dictionary.
466    """
467    return to_dict(v, reuse_instances=False, convert_sets=False)

Serialize object into dictionary.

def astuple(v: Any) -> tuple[typing.Any, ...]:
420def astuple(v: Any) -> tuple[Any, ...]:
421    """
422    Serialize object into tuple.
423    """
424    return to_tuple(v, reuse_instances=False, convert_sets=False)

Serialize object into tuple.

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

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:
96def init(debug: bool = False) -> None:
97    SETTINGS["debug"] = debug
logger = <Logger serde (WARNING)>
class ClassSerializer(typing.Protocol):
 998class ClassSerializer(Protocol):
 999    """
1000    Interface for custom class serializer.
1001
1002    This protocol is intended to be used for custom class serializer.
1003
1004    >>> from datetime import datetime
1005    >>> from serde import serde
1006    >>> from plum import dispatch
1007    >>> class MySerializer(ClassSerializer):
1008    ...     @dispatch
1009    ...     def serialize(self, value: datetime) -> str:
1010    ...         return value.strftime("%d/%m/%y")
1011    """
1012
1013    def serialize(self, value: Any) -> Any:
1014        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:
1013    def serialize(self, value: Any) -> Any:
1014        pass
class ClassDeserializer(typing.Protocol):
1017class ClassDeserializer(Protocol):
1018    """
1019    Interface for custom class deserializer.
1020
1021    This protocol is intended to be used for custom class deserializer.
1022
1023    >>> from datetime import datetime
1024    >>> from serde import serde
1025    >>> from plum import dispatch
1026    >>> class MyDeserializer(ClassDeserializer):
1027    ...     @dispatch
1028    ...     def deserialize(self, cls: type[datetime], value: Any) -> datetime:
1029    ...         return datetime.strptime(value, "%d/%m/%y")
1030    """
1031
1032    def deserialize(self, cls: Any, value: Any) -> Any:
1033        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:
1032    def deserialize(self, cls: Any, value: Any) -> Any:
1033        pass
def add_serializer(serializer: ClassSerializer) -> None:
1041def add_serializer(serializer: ClassSerializer) -> None:
1042    """
1043    Register custom global serializer.
1044    """
1045    GLOBAL_CLASS_SERIALIZER.append(serializer)

Register custom global serializer.

def add_deserializer(deserializer: ClassDeserializer) -> None:
1048def add_deserializer(deserializer: ClassDeserializer) -> None:
1049    """
1050    Register custom global deserializer.
1051    """
1052    GLOBAL_CLASS_DESERIALIZER.append(deserializer)

Register custom global deserializer.