Skip to main content
๐Ÿ›ก๏ธ Verified Technical Content: Written by Serhii Hrekov. | Last reviewed & updated in Git: August 4, 2026

Convert msgspec Struct to Python Dictionary

ยท 9 min read
Serhii Hrekov
Senior Software Engineer & System Architect specializing in Python, Web Systems, Cloud Infrastructure & Automation

When building high-throughput Python backend services, I frequently rely on msgspec as a lightweight, high-performance alternative to Pydantic and standard dataclasses. While msgspec.Struct objects provide blazing-fast serialization and low memory overhead thanks to their C-extension implementation, integration with legacy libraries, database ORMs, or third-party SDKs often requires converting these structures into native Python dictionaries (dict).

In this article, I will walk through the primary techniques for converting msgspec objects to dictionaries, compare their performance characteristics, and explain when to use each method in production backend systems.

Why Convert msgspec Structs to Dictionaries?โ€‹

During my work on real-time data pipelines and microservices, I encountered several common scenarios where msgspec.Struct instances needed to be transformed into native Python primitives:

  1. Third-Party Library Compatibility: Many Python SDKs (e.g., AWS Boto3, legacy web frameworks, MongoDB drivers) expect raw dict payloads.
  2. Dynamic Schema Manipulation: Modifying schema keys or merging payload dictionaries at runtime before storage or downstream processing.
  3. Serialization Boundaries: Transforming custom types (like datetime, UUID, or Enum values) into primitive strings or dictionaries for API responses.

Depending on whether you are working purely with msgspec.Struct instances or heterogenous object graphs containing non-struct types, msgspec provides dedicated C-accelerated helpers.


Method 1: Direct Conversion with msgspec.structs.asdictโ€‹

The canonical way to convert a msgspec.Struct instance to a Python dict is using msgspec.structs.asdict(). This function performs a recursive conversion of the Struct and any nested Struct instances into standard dictionaries.

How it Worksโ€‹

import msgspec

class User(msgspec.Struct):
name: str
age: int

# Create a msgspec Struct instance
user = User(name="Alice", age=30)

# Convert to Python dict
user_dict = msgspec.structs.asdict(user)

print(user_dict)
# Output: {'name': 'Alice', 'age': 30}
print(type(user_dict))
# Output: <class 'dict'>

Handling Nested Structs and Collectionsโ€‹

msgspec.structs.asdict() automatically traverses nested structs, lists, and mappings inside the struct:

import msgspec

class User(msgspec.Struct):
name: str
age: int

class Team(msgspec.Struct):
id: int
leader: User
members: list[User]

team = Team(
id=101,
leader=User(name="Alice", age=30),
members=[
User(name="Bob", age=25),
User(name="Charlie", age=35),
],
)

team_dict = msgspec.structs.asdict(team)

print(team_dict)
# Output:
# {
# 'id': 101,
# 'leader': {'name': 'Alice', 'age': 30},
# 'members': [
# {'name': 'Bob', 'age': 25},
# {'name': 'Charlie', 'age': 35}
# ]
# }

Important Caveats of msgspec.structs.asdictโ€‹

  • Struct Only: msgspec.structs.asdict() strictly expects a msgspec.Struct instance as its top-level argument. Passing a plain list, dict, or custom class raises a TypeError.
  • Preserves Object Types: Non-struct field values like datetime.datetime, uuid.UUID, or custom objects remain in their native Python representation rather than being transformed into primitive strings.

Method 2: Using msgspec.to_builtins for General Object Graphsโ€‹

When my data models contain mixed structures (such as top-level lists, tuples, sets, or custom non-struct objects), I use msgspec.to_builtins(). This function recursively converts any msgspec object graph into standard Python builtin primitives (dict, list, int, float, str, bool, None).

Converting Complex and Non-Struct Typesโ€‹

import msgspec
from datetime import datetime

class Event(msgspec.Struct):
title: str
timestamp: datetime
tags: set[str]

event = Event(
title="Deployment",
timestamp=datetime(2026, 8, 4, 12, 0, 0),
tags={"backend", "production"},
)

# Convert using to_builtins
builtins_payload = msgspec.to_builtins(event)

print(builtins_payload)
# Output: {'title': 'Deployment', 'timestamp': datetime.datetime(2026, 8, 4, 12, 0), 'tags': ['backend', 'production']}

Supporting Top-Level Collections and Custom Hooksโ€‹

Unlike asdict(), msgspec.to_builtins() works on arbitrary container structures, converting sets/tuples to lists and structs to dicts:

import msgspec

class Item(msgspec.Struct):
id: int
name: str

items = [Item(id=1, name="Widget A"), Item(id=2, name="Widget B")]

# Works seamlessly on top-level list of Structs
items_dict_list = msgspec.to_builtins(items)

print(items_dict_list)
# Output: [{'id': 1, 'name': 'Widget A'}, {'id': 2, 'name': 'Widget B'}]

msgspec.to_builtins() also supports enc_hook to define custom serialization behavior for arbitrary types that msgspec does not natively recognize.


Method 3: JSON Serialization Round-Tripโ€‹

In microservice boundaries where you need string primitives for dates, UUIDs, or decimal values suitable for REST/gRPC responses, a JSON round-trip using msgspec.json.encode() and json.loads() (or standard deserialization) is a clean approach.

import msgspec
import json
from datetime import datetime

class Order(msgspec.Struct):
order_id: str
created_at: datetime
total: float

order = Order(
order_id="ord-9921",
created_at=datetime.utcnow(),
total=149.99,
)

# Encode msgspec struct to optimized JSON bytes
json_bytes = msgspec.json.encode(order)

# Decode JSON bytes into a Python dict with primitive types (ISO-8601 string for datetime)
order_dict = json.loads(json_bytes)

print(order_dict)
# Output: {'order_id': 'ord-9921', 'created_at': '2026-08-04T10:23:43Z', 'total': 149.99}

When to Choose JSON Round-Trippingโ€‹

  • ISO-8601 Formatting: Automatically converts datetime objects to standard string representations.
  • Primitive Enforcement: Ensures all values in the resulting dictionary are strictly standard JSON-compatible primitives (str, int, float, bool, None, list, dict).

Performance and Architectural Comparisonโ€‹

In high-throughput microservices handling tens of thousands of requests per second, choice of conversion method impacts latency and CPU allocation.

MethodTarget InputDatetime / UUID HandlingPerformancePrimary Use Case
msgspec.structs.asdict()msgspec.Struct onlyPreserved as Python objectsExtremely fast (C extension)In-memory backend object transformation
msgspec.to_builtins()Any object / containerPreserved / converted to listsFast (C extension)Heterogeneous schemas, sets/tuples conversion
msgspec.json.encode() + json.loads()Any serializable objectFormatted to JSON primitives (str)Medium (encoding + parsing)API response payloads & SDK boundary crossing

Benchmarking Insightsโ€‹

In my benchmarks comparing dictionary conversion speeds across Python data libraries:

  1. msgspec.structs.asdict() operates significantly faster than standard library dataclasses.asdict() because msgspec executes its traversal directly within its optimized C layer.
  2. Direct conversion via asdict() or to_builtins() avoids string allocation overhead, making it preferred over JSON round-tripping whenever native Python objects are acceptable.

Converting msgspec structs to dictionaries in Python is straightforward and offers high performance:

  • Use msgspec.structs.asdict(obj) for direct, high-speed conversion of msgspec.Struct instances.
  • Use msgspec.to_builtins(obj) when working with complex object graphs, top-level lists/tuples, or when custom type encoding hooks are required.
  • Use msgspec.json.encode() with json.loads() when you need JSON-compliant primitive types (such as stringified timestamps).

Official Referencesโ€‹