Usage

structtype provides a single serialization protocol: JSON. All JSON operations are available as methods on Struct instances, or via StructAdapter for encoding non-struct objects.

Encoding

Struct instances are encoded to JSON using struct_dump_json():

>>> from structtype import Struct

>>> class User(Struct):
...     name: str
...     groups: set[str] = set()
...     email: str | None = None

>>> alice = User("alice")
>>> alice.struct_dump_json()
b'{"name":"alice","groups":[],"email":null}'

For encoding non-struct objects, StructAdapter can be used:

>>> from structtype import StructAdapter

>>> StructAdapter(dict).struct_dump_json({"hello": "world"})
b'{"hello":"world"}'

Decoding

JSON is decoded into a struct instance using struct_validate_json():

>>> User.struct_validate_json(b'{"name":"alice","groups":[],"email":null}')
User(name='alice', groups=set(), email=None)

Typed Decoding

structtype validates data during decoding against the struct’s type annotations. If a message doesn’t match the expected type, an error is raised with a clear message:

>>> User.struct_validate_json(
...     b'{"name": "bill", "groups": ["devops", 123]}'
... )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
structtype.ValidationError: Expected `str`, got `int` - at `$.groups[1]`

“Strict” vs “Lax” Mode

structtype won’t perform 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 casting:

>>> User.struct_validate_json(
...     b'{"name":"alice","groups":[1,2,3]}'
... )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
structtype.ValidationError: Expected `str`, got `int` - at `$.groups[0]`

For cases where you’d like a more lax set of conversion rules, pass strict=False:

>>> User.struct_validate_json(
...     b'{"name":"alice","groups":["admin"],"email":null}',
...     strict=False
... )
User(name='alice', groups={'admin'}, email=None)

See Supported Types for how lax mode affects individual types.

Converting to and from Builtin Types

In some cases, structtype only needs to process part of a message, and the rest is handled by another library. For these situations, struct_dump and struct_validate convert between high-level types and plain builtin types (dict, list, str, int, …) without going through an encoded representation.

>>> from structtype import Struct

>>> class User(Struct, omit_defaults=True):
...     name: str
...     groups: set[str] = set()
...     email: str | None = None

>>> alice = User("alice")

>>> # struct_dump applies omit_defaults and expands nested types
... alice.struct_dump()
{'name': 'alice'}

>>> # struct_validate is the inverse operation
... User.struct_validate({"name": "bill", "groups": ["devops"]})
User(name='bill', groups={'devops'}, email=None)

See Converters for a more detailed guide, including how to use these functions to add structtype support for additional serialization protocols.

Note that struct_to_dict and struct_to_tuple are not equivalent to struct_dump. They perform 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 structtype.UNSET, rename and tag are ignored, nested structtype.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 struct_dump when the output is intended for serialization.