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 <structs>` 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 <supported-types>`.

Here we define a user schema as a :doc:`Struct <structs>` 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 "<stdin>", line 1, in <module>
    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 "<stdin>", line 1, in <module>
    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 <struct-tagged-unions>`.
  - Omission of :ref:`UNSET <unset-type>` 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`,
    ``frozendict`` to `dict`, `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
