Convert msgspec Struct to Python Dictionary
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:
- Third-Party Library Compatibility: Many Python SDKs (e.g., AWS Boto3, legacy web frameworks, MongoDB drivers) expect raw
dictpayloads. - Dynamic Schema Manipulation: Modifying schema keys or merging payload dictionaries at runtime before storage or downstream processing.
- 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 amsgspec.Structinstance as its top-level argument. Passing a plainlist,dict, or custom class raises aTypeError. - 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
datetimeobjects 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.
| Method | Target Input | Datetime / UUID Handling | Performance | Primary Use Case |
|---|---|---|---|---|
msgspec.structs.asdict() | msgspec.Struct only | Preserved as Python objects | Extremely fast (C extension) | In-memory backend object transformation |
msgspec.to_builtins() | Any object / container | Preserved / converted to lists | Fast (C extension) | Heterogeneous schemas, sets/tuples conversion |
msgspec.json.encode() + json.loads() | Any serializable object | Formatted 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:
msgspec.structs.asdict()operates significantly faster than standard librarydataclasses.asdict()becausemsgspecexecutes its traversal directly within its optimized C layer.- Direct conversion via
asdict()orto_builtins()avoids string allocation overhead, making it preferred over JSON round-tripping whenever native Python objects are acceptable.
Summary & Reference Linksโ
Converting msgspec structs to dictionaries in Python is straightforward and offers high performance:
- Use
msgspec.structs.asdict(obj)for direct, high-speed conversion ofmsgspec.Structinstances. - 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()withjson.loads()when you need JSON-compliant primitive types (such as stringified timestamps).
