Composite Fields

Composite fields combine multiple values or conditional logic.

Available Types

  • Array - Repeated field values

  • EmbeddedStruct - Nested struct

  • Conditional - Optional field based on condition

  • Switch - Tagged union (variant type)

Array

Array of repeated field values:

from pystructs import Struct, UInt8, UInt16, Array, Ref

class ScoreBoard(Struct):
    count = UInt8()
    scores = Array(UInt16(), count=Ref("count"))

board = ScoreBoard.parse(b"\x03\x64\x00\xc8\x00\x2c\x01")
print(board.scores)  # [100, 200, 300]

EmbeddedStruct

Nested struct within another struct:

from pystructs import Struct, UInt32, UInt8, EmbeddedStruct

class Header(Struct):
    magic = UInt32(default=0xDEADBEEF)
    version = UInt8(default=1)

class Packet(Struct):
    header = EmbeddedStruct(Header)
    payload_size = UInt16()

packet = Packet.parse(b"\xef\xbe\xad\xde\x01\x00\x10")
print(packet.header.magic)  # 3735928559

Conditional

Field that only exists when a condition is met:

from pystructs import Struct, UInt8, UInt32, Conditional, Ref

class VersionedPacket(Struct):
    version = UInt8()
    # Only present in version 2+
    extended_header = Conditional(
        UInt32(),
        when=Ref("version") >= 2,
    )
    data = UInt8()

# Version 1: no extended header
v1 = VersionedPacket.parse(b"\x01\x42")
print(v1.extended_header)  # None

# Version 2: with extended header
v2 = VersionedPacket.parse(b"\x02\x00\x00\x00\x01\x42")
print(v2.extended_header)  # 1

Switch

Tagged union - select struct based on discriminator:

from pystructs import Struct, UInt8, UInt16, Switch, Ref

class TextPayload(Struct):
    length = UInt8()

class BinaryPayload(Struct):
    size = UInt16()

class Message(Struct):
    msg_type = UInt8()
    payload = Switch(
        discriminator=Ref("msg_type"),
        cases={
            1: TextPayload,
            2: BinaryPayload,
        },
    )

text_msg = Message.parse(b"\x01\x05")
print(text_msg.payload.length)  # 5

API Reference

class pystructs.fields.composite.Array(item_field: BaseField, count: int | Ref, default: List | None = None, required: bool = True, validators: List[Callable] | None = None)[source]

Array of repeated fields.

Count can be specified as a fixed integer or as a Ref to another field.

Examples:
>>> class Packet(Struct):
...     count = UInt16()
...     items = Array(UInt32(), count=Ref('count'))
...
>>> class FixedArray(Struct):
...     values = Array(UInt8(), count=10)
get_count(instance: Struct) int[source]

Get the current count of items.

Args:

instance: The struct instance

Returns:

Number of items

get_size(instance: Struct) int[source]

Get the total size of this array.

Args:

instance: The struct instance

Returns:

Total size in bytes

parse(buffer: BinaryIO, instance: Struct) List[Any][source]

Parse array from buffer.

Args:

buffer: Binary stream to read from instance: The struct instance being parsed

Returns:

List of parsed items

serialize(value: List[Any], instance: Struct) bytes[source]

Serialize array to bytes.

Note: This is “dumb” serialization - no count validation.

Args:

value: List of items to serialize instance: The struct instance being serialized

Returns:

Serialized bytes

class pystructs.fields.composite.EmbeddedStruct(struct_class: Type[Struct], default: Struct | None = None, required: bool = True, validators: List[Callable] | None = None)[source]

Embedded struct field.

Contains a nested Struct as a field value. The nested struct maintains a parent reference for Ref path resolution.

Examples:
>>> class Header(Struct):
...     magic = UInt32(default=0xDEADBEEF)
...     version = UInt8(default=1)
...
>>> class Packet(Struct):
...     header = EmbeddedStruct(Header)
...     data = Bytes(size=10)
get_size(instance: Struct) int[source]

Get the size of the embedded struct.

Args:

instance: The struct instance

Returns:

Size in bytes

parse(buffer: BinaryIO, instance: Struct) Struct[source]

Parse embedded struct from buffer.

Args:

buffer: Binary stream to read from instance: The parent struct instance

Returns:

Parsed nested Struct instance

serialize(value: Struct, instance: Struct) bytes[source]

Serialize embedded struct to bytes.

Args:

value: The nested Struct to serialize instance: The parent struct instance

Returns:

Serialized bytes

class pystructs.fields.composite.Conditional(field: BaseField, when: Callable[[Struct], bool] | RefComparison | RefLogical, default: Any = None, required: bool = False, validators: List[Callable] | None = None)[source]

Conditional field that only exists when a condition is met.

The when parameter determines if the field exists. It can be: - A callable taking the instance and returning bool - A RefComparison (e.g., Ref(‘version’) >= 2) - A RefLogical (combined comparisons)

Examples:
>>> class Packet(Struct):
...     version = UInt8()
...     # Only present in version 2+
...     extra_data = Conditional(UInt32(), when=Ref('version') >= 2)
...
>>> class Message(Struct):
...     flags = UInt8()
...     # Present when flags bit 0 is set
...     optional = Conditional(UInt16(), when=lambda s: s.flags & 1)
get_size(instance: Struct) int[source]

Get the size of the conditional field.

Args:

instance: The struct instance

Returns:

Size in bytes (0 if condition not met)

parse(buffer: BinaryIO, instance: Struct) Any[source]

Parse conditional field from buffer.

Args:

buffer: Binary stream to read from instance: The struct instance being parsed

Returns:

Parsed value or None if condition not met

serialize(value: Any, instance: Struct) bytes[source]

Serialize conditional field to bytes.

Args:

value: The value to serialize instance: The struct instance being serialized

Returns:

Serialized bytes (empty if condition not met)

class pystructs.fields.composite.Switch(discriminator: Ref | Callable[[Struct], Any], cases: Dict[Any, BaseField | Type[Struct]], default: BaseField | Type[Struct] | None = None, required: bool = True, validators: List[Callable] | None = None)[source]

Field that can be one of multiple types based on a discriminator.

Switch selects between different field types based on the value of another field. This is useful for tagged unions and variant types.

Examples:
>>> class Message(Struct):
...     msg_type = UInt8()
...     payload = Switch(
...         discriminator=Ref('msg_type'),
...         cases={
...             1: TextPayload,
...             2: BinaryPayload,
...             3: StatusPayload,
...         },
...         default=RawPayload,
...     )
get_size(instance: Struct) int[source]

Get the size of the selected field.

Args:

instance: The struct instance

Returns:

Size in bytes

parse(buffer: BinaryIO, instance: Struct) Any[source]

Parse the selected field from buffer.

Args:

buffer: Binary stream to read from instance: The struct instance being parsed

Returns:

Parsed value

Raises:

ValueError: If no matching case and no default

serialize(value: Any, instance: Struct) bytes[source]

Serialize the selected field to bytes.

Args:

value: The value to serialize instance: The struct instance being serialized

Returns:

Serialized bytes