Usage ===== ``msgspec`` supports multiple serialization protocols, accessed through separate submodules: - ``msgspec.json`` (JSON_) - ``msgspec.msgpack`` (MessagePack_) - ``msgspec.yaml`` (YAML_) - ``msgspec.toml`` (TOML_) Each supports a consistent interface, making it simple to switch between protocols as needed. Encoding -------- Each submodule has an ``encode`` method for encoding Python objects using the respective protocol. .. code-block:: python >>> import msgspec >>> # Encode as JSON ... msgspec.json.encode({"hello": "world"}) b'{"hello":"world"}' >>> # Encode as msgpack ... msgspec.msgpack.encode({"hello": "world"}) b'\x81\xa5hello\xa5world' Note that if you're making multiple calls to ``encode``, it's more efficient to create an ``Encoder`` once and use the ``Encoder.encode`` method instead. .. code-block:: python >>> import msgspec >>> # Create a JSON encoder ... encoder = msgspec.json.Encoder() >>> # Encode as JSON using the encoder ... encoder.encode({"hello": "world"}) b'{"hello":"world"}' Decoding -------- Each submodule has ``decode`` method for decoding messages using the respective protocol. .. code-block:: python >>> import msgspec >>> # Decode JSON ... msgspec.json.decode(b'{"hello":"world"}') {'hello': 'world'} >>> # Decode msgpack ... msgspec.msgpack.decode(b'\x81\xa5hello\xa5world') {'hello': 'world'} Note that if you're making multiple calls to ``decode``, it's more efficient to create a ``Decoder`` once and use the ``Decoder.decode`` method instead. .. code-block:: python >>> import msgspec >>> # Create a JSON decoder ... decoder = msgspec.json.Decoder() >>> # Decode JSON using the decoder ... decoder.decode(b'{"hello":"world"}') {'hello': 'world'} .. _typed-decoding: Typed Decoding -------------- ``msgspec`` optionally supports specifying the expected output types during decoding. This serves a few purposes: - Often serialized data has a fixed schema (e.g. a request handler in a REST api expects a certain JSON structure). Specifying the expected types allows ``msgspec`` to perform validation during decoding, with *no* added runtime cost. - Python has a much richer type system than serialization protocols like JSON_ or MessagePack_. Specifying the output types lets ``msgspec`` decode messages into types other than the defaults described above (e.g. decoding JSON objects into a :doc:`Struct ` instead of the default `dict`). - The `type annotations`_ used to describe the expected types are compatible with tools like mypy_ or pyright_, providing excellent editor integration. ``msgspec`` uses Python `type annotations`_ to describe the expected types. A :doc:`wide variety of builtin types are supported `. Here we define a user schema as a :doc:`Struct ` type. We then pass the type to ``decode`` via the ``type`` keyword argument: .. code-block:: python >>> import msgspec >>> class User(msgspec.Struct): ... name: str ... groups: set[str] = set() ... email: str | None = None >>> msgspec.json.decode( ... b'{"name": "alice", "groups": ["admin", "engineering"]}', ... type=User ... ) User(name='alice', groups={'admin', 'engineering'}, email=None) If a message doesn't match the expected type, an error is raised. .. code-block:: python >>> msgspec.json.decode( ... b'{"name": "bill", "groups": ["devops", 123]}', ... type=User ... ) Traceback (most recent call last): File "", line 1, in msgspec.ValidationError: Expected `str`, got `int` - at `$.groups[1]` .. _strict-vs-lax: "Strict" vs "Lax" Mode ~~~~~~~~~~~~~~~~~~~~~~ Unlike some other libraries (e.g. pydantic_), ``msgspec`` won't perform any unsafe implicit conversion by default ("strict" mode). For example, if an integer is specified and a string is provided instead, an error is raised rather than attempting to cast the string to an int. .. code-block:: python >>> msgspec.json.decode(b'[1, 2, "3"]', type=list[int]) Traceback (most recent call last): File "", line 1, in msgspec.ValidationError: Expected `int`, got `str` - at `$[2]` For cases where you'd like a more lax set of conversion rules, you can pass ``strict=False`` to any ``decode`` function or ``Decoder`` class ("lax" mode). See :doc:`supported-types` for information on how this affects individual types. .. code-block:: python >>> msgspec.json.decode(b'[1, 2, "3"]', type=list[int], strict=False) [1, 2, 3] .. _to-builtins-vs-asdict: Converting to and from Builtin Types ------------------------------------ In some cases, ``msgspec`` only needs to process part of a message, and the rest is handled by another library. For these situations, `msgspec.to_builtins` and `msgspec.convert` convert between high-level types and plain builtin types (`dict`, `list`, `str`, `int`, ...) without going through an encoded representation. - `msgspec.to_builtins` is the "encoding" half. It applies the same semantics as `msgspec.json.encode` / `msgspec.msgpack.encode` - just with builtin Python types as the output rather than an encoded byte string. This includes: - Struct-level settings: ``rename``, :ref:`omit_defaults`, ``array_like``, and ``tag`` for :ref:`tagged unions `. - Omission of :ref:`UNSET ` fields. - Recursive expansion of nested `msgspec.Struct`, `dataclasses.dataclass`, attrs_, `typing.TypedDict`, and `typing.NamedTuple` values. - Value-level conversions of types that don't map directly to builtin types: `bytes` / `bytearray` / `memoryview` to base64 string, `datetime.datetime` / `datetime.date` / `datetime.time` / `datetime.timedelta` to ISO 8601 string, `uuid.UUID` and `decimal.Decimal` to string, `set` / `frozenset` to `list`, `enum.Enum` to its member value. - Optional ``enc_hook``, ``str_keys``, ``order``, and ``builtin_types`` kwargs for tuning the output to the wrapping protocol. - `msgspec.convert` is the "decoding" half: it takes builtin types and validates them against a schema, producing high-level types. .. code-block:: python >>> import msgspec >>> class User(msgspec.Struct, omit_defaults=True): ... name: str ... groups: set[str] = set() ... email: str | None = None >>> alice = User("alice") >>> # to_builtins applies omit_defaults and expands nested types ... msgspec.to_builtins(alice) {'name': 'alice'} >>> # convert is the inverse operation ... msgspec.convert({"name": "bill", "groups": ["devops"]}, User) User(name='bill', groups={'devops'}, email=None) See :doc:`converters` for a more detailed guide, including how to use these functions to add ``msgspec`` support for additional serialization protocols. Note that `msgspec.structs.asdict` and `msgspec.structs.astuple` are *not* equivalent to `msgspec.to_builtins`. They are modeled on `dataclasses.asdict` / `dataclasses.astuple`: a one-to-one conversion of a single struct instance to a `dict` or `tuple`, using the raw attribute names. None of the semantics listed above apply. Every field is included regardless of ``omit_defaults`` or `msgspec.UNSET`, ``rename`` and ``tag`` are ignored, nested `msgspec.Struct` / `dataclasses.dataclass` / attrs_ values are left as-is, and value-level types (`bytes`, `datetime.datetime`, `uuid.UUID`, `decimal.Decimal`, `enum.Enum`, ...) are not converted. Prefer `msgspec.to_builtins` when the output is intended for serialization. .. _JSON: https://json.org .. _MessagePack: https://msgpack.org .. _YAML: https://yaml.org .. _TOML: https://toml.io/en/ .. _type annotations: https://docs.python.org/3/library/typing.html .. _pydantic: https://pydantic.dev/docs/validation/latest/get-started/ .. _mypy: https://mypy.readthedocs.io/en/stable/ .. _pyright: https://github.com/microsoft/pyright .. _attrs: https://www.attrs.org/en/stable/index.html