Mashumaro is a fast serialization library built around Python type annotations and dataclasses. It generates specialized packing and unpacking functions for your exact type shape, so the normal hot path does not repeatedly inspect fields or walk annotations.
Use a mixin when the root object is a dataclass and methods such as to_json() are the most convenient API. Use a reusable codec when the root shape is anything else — for example list[User], dict[str, Event | None], a TypedDict, or a scalar type.
Mashumaro 3.23 supports Python 3.10–3.15. For older Python versions, use the last compatible Mashumaro release listed below.
Python
Recommended mashumaro version
Status
3.10–3.15
Current release
Supported
3.9
3.20
Last compatible release
3.8
3.14
Last compatible release
3.7
3.9.1
Last compatible release
3.6
3.1.1
Last compatible release
A Python version that has reached end of life no longer receives fixes from CPython. Pinning an old mashumaro release preserves compatibility, but upgrading Python is the safer choice.
The dictionary and standard-library JSON APIs need no extra dependencies. Install only the package extras required by your wire formats:
bash
pipinstall"mashumaro[orjson]"pipinstall"mashumaro[yaml]"pipinstall"mashumaro[toml]"pipinstall"mashumaro[msgpack]"# Multiple extras may be installed togetherpipinstall"mashumaro[orjson,yaml,toml,msgpack]"
Declare ordinary dataclasses and add a mixin only to the root model that needs serialization methods. Nested dataclasses do not need to inherit a mixin.
to_dict() does not mean dataclasses.asdict(). Mashumaro converts values according to their annotations: UUIDs and dates become strings, sets become lists, nested dataclasses become dictionaries, and custom strategies are applied.
Mixins or codecs?
Both APIs use the same generated conversion engine. Choose the entry point that expresses the root shape most naturally.
Question
Mixin
Codec
Root value
Dataclass instance
Any supported type shape
API
Methods on the model
Reusable encoder/decoder object
Compile time
Class creation, or first use with lazy compilation
Format dialects can deliberately keep native values in the basic form. For example, MessagePack keeps bytes, TOML keeps native date/time/datetime, and orjson handles several native scalar types itself. The Supported Types chapter documents these differences.
Conversion and validation
Deserialization is annotation-directed conversion, not merely assignment:
When conversion fails, Mashumaro raises a typed exception such as InvalidFieldValue; missing and unexpected keys have separate exceptions. See Errors and Troubleshooting.
Mashumaro is not a business-rule validator. Constraints such as “price must be positive” belong in your model, a validation layer, or generated JSON Schema. Serialization hooks can normalize data but should not hide invalid domain states.
Recommended project pattern
For a medium or large codebase:
Put shared defaults in a project base mixin with an inner Config.
Keep wire-format choices at system boundaries.
Use field options for genuinely local exceptions.
Use reusable SerializationStrategy objects for third-party types.
Use dialects when the same model must speak more than one external representation.
Reuse codecs for repeated serialization of the same root shape.
Enable forbid_extra_keys at strict API boundaries.
Mashumaro provides a dictionary “basic form” plus JSON, orjson, YAML, TOML, and MessagePack integrations. Every integration has a reusable codec API; dataclass roots also have mixins with format-specific methods.
API overview
Format
Mixin methods
Reusable codec
One-off functions
Encoded result
Basic form
to_dict, from_dict
BasicEncoder, BasicDecoder
encode, decode
Python object
JSON
to_json, from_json
JSONEncoder, JSONDecoder
json_encode, json_decode
str by default
orjson
to_jsonb, to_json, from_json
ORJSONEncoder, ORJSONDecoder
json_encode, json_decode
bytes from encoder/to_jsonb
YAML
to_yaml, from_yaml
YAMLEncoder, YAMLDecoder
yaml_encode, yaml_decode
str or bytes
TOML
to_toml, from_toml
TOMLEncoder, TOMLDecoder
toml_encode, toml_decode
str
MessagePack
to_msgpack, from_msgpack
MessagePackEncoder, MessagePackDecoder
msgpack_encode, msgpack_decode
bytes
Each module also exports its one-off functions as encode and decode, so an application can use a uniform import style.
Basic form
The basic form is the typed conversion layer used underneath most other formats. It is also useful directly when passing values to a database driver, web framework, template renderer, or another serializer.
Every format mixin derives from DataClassDictMixin, so do not inherit it separately when you already use DataClassJSONMixin, DataClassYAMLMixin, or another format mixin.
Reusable and one-off codecs
python
frommashumaro.codecsimportBasicDecoder, BasicEncoderfrommashumaro.codecs.basicimportdecode, encode
shape =dict[str, list[Release]]
value = {"stable": [release]}
encoder =BasicEncoder(shape)
decoder =BasicDecoder(shape)
basic = encoder.encode(value)
assert decoder.decode(basic) == value
assert decode(encode(value, shape), shape) == value
BasicDecoder accepts an optional pre_decoder_func; BasicEncoder accepts post_encoder_func. These functions wrap the generated typed conversion and are useful for integrating a storage adapter without defining a new codec.
Standard-library JSON
The standard JSON integration uses json.loads and json.dumps by default and requires no extra package.
Dataclass mixin
python
fromdataclassesimportdataclassfrommashumaro.mixins.jsonimportDataClassJSONMixin@dataclassclassPoint(DataClassJSONMixin):
x: int
y: int
text =Point(10, 20).to_json()
assertPoint.from_json(text) ==Point(10, 20)
to_json() accepts a replacement encoder as its first argument and forwards other keyword arguments to to_dict(). from_json() similarly accepts a decoder and forwards the rest to from_dict().
The decoder accepts str, bytes, or bytearray. The default encoder returns str, but a custom post_encoder_func may define a different boundary contract.
The orjson documentation describes its bytes-oriented API and native types. orjson produces UTF-8 JSON bytes and natively handles datetime, date, time, and UUID on the serialization side. Mashumaro's OrjsonDialect passes those values through so orjson can encode them.
The reusable API is ORJSONEncoder(shape) and ORJSONDecoder(shape) from mashumaro.codecs.orjson. The encoder returns bytes; the one-off json_encode and encode functions do the same.
orjson is not simply a drop-in speed flag for standard JSON. Its option set, return type, native-type handling, and error behavior come from orjson. Choose the integration as part of your external API contract.
Mashumaro selects CSafeLoader when available and falls back to SafeLoader; for dumping it selects the C dumper when available. You can supply another encoder/decoder to the mixin or a post_encoder_func/pre_decoder_func to the codec:
Only load YAML from trusted or appropriately restricted sources. Mashumaro controls typed conversion after parsing; the safety characteristics of parsing are defined by the supplied PyYAML loader.
TOML
Install the TOML extra:
bash
pipinstall"mashumaro[toml]"
Python 3.11+ uses the standard-library tomllib for reading. Python 3.10 uses tomli. Writing uses tomli-w on every supported Python version.
TOML 1.0 has native date/time values, so TOMLDialect passes datetime, date, and time through to tomli-w. TOML has no null value, so fields containing None are omitted by default. TOML documents are tables at the top level; use a dataclass or mapping-compatible shape rather than a scalar root.
Reusable codecs are TOMLEncoder and TOMLDecoder; one-off functions are toml_encode/toml_decode and their encode/decode aliases.
MessagePackDialect passes bytes, bytearray, memoryview, and Buffer through as native binary values. It reconstructs declared bytearray and memoryview fields explicitly, while an abstract Buffer field is decoded as bytes. The default encoder calls msgpack.packb(..., use_bin_type=True) and the default decoder calls msgpack.unpackb(..., raw=False).
Use codec transforms to customize msgpack options without changing the typed layer:
All reusable codecs follow the same two-stage idea:
A decoder's pre_decoder_func turns an encoded payload into the basic form before typed deserialization.
An encoder's post_encoder_func turns the generated basic form into the final payload.
The basic codec permits None for either function; YAML and MessagePack do as well. This makes codecs useful even when another system already parses or renders the transport format.
The standard-library urllib.parse documentation defines the query-string behavior of urlencode and parse_qs used in this adapter.
Choosing a format
Need
Good default
Public interoperable API
Standard JSON
Maximum JSON throughput and bytes output
orjson
Human-edited nested configuration
YAML, with deliberate safe-loader policy
Human-edited application configuration with strict semantics
TOML
Compact internal binary messages
MessagePack
Already have a transport/storage adapter
Basic codec with transforms
Serialization format does not replace a compatibility strategy. For durable data, define aliases, defaults, discriminators, and migration policy explicitly; test old payloads against new models.
Mashumaro supports dataclasses, standard collections, modern typing constructs, enums, date/time objects, slices, paths, network addresses, decimals, UUIDs, patterns, and user-defined extensions. Support applies recursively: a type can appear at the root of a codec, as a dataclass field, inside a collection, or as a type argument of another supported generic.
This chapter describes the default basic representation. A format-specific dialect may keep selected native values — notably bytes in MessagePack, date/time values in TOML, and several scalars in orjson.
Representation reference
Scalars and special values
Python type
Basic serialized form
Deserialization behavior
None / NoneType
None
Produces None
str
str
Calls str conversion where needed
int
int
Calls int conversion and wraps failure
float
float
Calls float conversion and wraps failure
bool
bool
Calls bool conversion
bytes
Base64 ASCII str
Base64-decodes to bytes
bytearray
Base64 ASCII str
Base64-decodes to bytearray
memoryview
Base64 ASCII str
Base64-decodes to a memoryview over bytes
Buffer
Base64 ASCII str
Base64-decodes to bytes
Any
Passed through
Passed through without typed conversion
The default binary encoder uses base64.encodebytes, whose output includes a trailing newline:
MessagePack overrides this basic behavior and stores binary values natively. For URL-safe or newline-free Base64 JSON, define a SerializationStrategy.
The default encoder accepts contiguous, single-byte memoryview values. Deserialization creates a new view over decoded bytes, so the original exporter's mutability, format, shape, and strides are not preserved.
The abstract Buffer annotation accepts buffer-protocol values. Import it from typing_extensions on Python 3.10 and 3.11. Because the annotation does not name a concrete buffer implementation, deserialization produces bytes.
String representations preserve decimal precision and the exact fraction value across JSON-compatible formats. RoundedDecimal is a built-in strategy for applying decimal quantization at serialization time.
Mashumaro supports pathlib.Path, PurePath, PosixPath, PurePosixPath, WindowsPath, PureWindowsPath, custom subclasses, and os.PathLike. Values are serialized with os.fspath() and reconstructed according to the annotation and operating system.
re.Pattern, re.Pattern[str], re.Pattern[bytes], and typing.Pattern serialize to their .pattern value and deserialize with re.compile. String patterns produce strings; bytes patterns produce bytes.
Slices
A slice is serialized as a three-element list [start, stop, step]. Because the encoded form is a list, a slice cannot be used as a mapping key in JSON, TOML, YAML, or MessagePack.
On Python 3.15+, slice is generic. Mashumaro recursively converts each component according to its type argument and follows the type-parameter defaults used by type checkers:
Annotation
Effective component types
slice
slice[Any, Any, Any]
slice[A]
slice[A, A, A]
slice[A, B]
slice[A, B, A \| B]
slice[A, B, C]
slice[A, B, C]
Other arities are rejected. Each component may also be None. A common example is a date range whose step is a timedelta:
Both legacy names from typing and PEP 585 built-in generic syntax are supported. On supported Python versions, prefer list[int] and dict[str, User] unless your project needs a compatibility style.
The built-in frozendict is available on Python 3.15 and newer. Its keys and values are converted recursively like those of other mappings, while deserialization preserves the immutable frozendict type.
Tuples
Fixed, variable, and empty tuple shapes retain their typed meaning even though their basic representation is a list:
The basic codec can convert typed mapping keys recursively, but the final format still sets the wire constraint:
JSON object keys are strings.
TOML keys are strings.
YAML and MessagePack can represent more key types, but downstream consumers may not.
slice is encoded as a list, so it cannot be a mapping key in any of these formats.
For an interoperable JSON/TOML contract, prefer dict[str, V] or define a strategy that turns keys into an unambiguous string.
Copy behavior
Collections are normally copied while their elements are converted. A dialect's no_copy_collections option can pass selected collection types through when it is safe. Built-in format dialects use this for some native containers; custom use is an optimization that can expose mutable input objects to the downstream encoder.
Dataclasses
Nested dataclasses are supported even if only the root class inherits a mixin:
Supported dataclass features include inheritance, slots=True and kw_only=True, defaults, default factories, forward references, recursive models, generic dataclasses, ClassVar, InitVar, and typing.Self.
ClassVar is not an instance field and is ignored. InitVar participates in construction rather than stored output, following dataclass semantics.
Named tuples
Typed and untyped named tuples are supported, including defaults and generic named tuples. The default representation is a list. Set namedtuple_as_dict = True globally, use a dialect, or select as_dict for one field.
When dictionary representation is enabled, missing named-tuple items with defaults use those defaults during deserialization.
Typed dictionaries
TypedDict can be a field type or codec root. Mashumaro understands total and non-total dictionaries, Required, NotRequired, and ReadOnly markers from both typing and typing_extensions.
Literal supports strings, integers, booleans, None, bytes, and enum members. A value outside the allowed literal set causes InvalidFieldValue.
To encode all enum subclasses by name instead of by value, register a strategy with match_subclasses=True.
Optional and union types
Both typing.Optional[T]/typing.Union[A, B] and PEP 604T | None/A | B are supported. Recursive unions and unions nested inside collections are supported as well.
An untagged union can be ambiguous when variants accept the same representation — for example bool | int or multiple dataclasses with overlapping fields. Use Discriminator for a stable polymorphic wire contract.
Other typing constructs
Annotated
The underlying type is serialized normally. Mashumaro-specific metadata such as Alias and Discriminator, plus JSON Schema annotations, can be attached with Annotated without replacing the type:
Type variables are resolved from a concrete generic use. Unbound variables fall back to their bound, constraint, default, or Any as appropriate. TypeVar defaults from PEP 696 are supported through typing_extensions and natively on newer Python versions.
Final, LiteralString, Self, and ReadOnly
These markers are supported from typing where available and from typing_extensions otherwise. They refine static meaning while Mashumaro converts the underlying runtime value.
PEP 695 type aliases
On Python 3.12+, the PEP 695 type statement is supported as a codec shape or field annotation, including parameterized and recursive aliases:
Recursive aliases are guarded during generated-code and JSON Schema construction.
Python version guide
The package supports Python 3.10–3.15. Many newer typing objects can be used on older supported interpreters through the mandatory typing_extensions dependency, but new grammar cannot be backported.
Feature
Native Python
Earlier supported Python
A | B union syntax, list[int]
3.10
All supported versions already have it
Required, NotRequired
3.11
Import from typing_extensions on 3.10
Self
3.11
Import from typing_extensions
TypeVarTuple, Unpack
3.11
Import from typing_extensions
PEP 695 class Box[T] and type Alias = ... syntax
3.12
No syntax backport; use Generic and assignment aliases
TypeVar defaults (PEP 696)
3.13
Use typing_extensions.TypeVar(default=...)
ReadOnly for TypedDict
3.13
Import from typing_extensions
Deferred annotation evaluation model
3.14
Use string references or from __future__ import annotations
Generic syntax by Python version
The following two definitions express the same model.
Python 3.10 and 3.11:
python
fromdataclassesimportdataclassfromtypingimportGeneric, TypeVarT=TypeVar("T")
@dataclassclassBox(Generic[T]):
value: T
Python 3.12 and newer:
python
fromdataclassesimportdataclass@dataclassclassBox[T]:
value: T
Both forms work as Box[date] fields and codec roots. See Generics and Modern Typing for inheritance, variadic generics, forward references, and generic extension types.
Format-specific representation differences
Type
Basic/JSON/YAML default
orjson default
TOML default
MessagePack default
bytes
Base64 string
Base64 string
Base64 string
Native binary
bytearray
Base64 string
Base64 string
Base64 string
Native binary, restored as bytearray
memoryview
Base64 string
Base64 string
Base64 string
Native binary, restored as memoryview
Buffer
Base64 string
Base64 string
Base64 string
Native binary, restored as bytes
datetime
ISO string
Passed to orjson
Native TOML datetime
ISO string
date
ISO string
Passed to orjson
Native TOML date
ISO string
time
ISO string
Passed to orjson
Native TOML time
ISO string
UUID
String
Passed to orjson
String
String
None field
Present unless omitted by config
Present unless omitted
Omitted by TOML dialect
Present unless omitted
Format behavior is implemented with dialects, so you can merge it with your own representation rules instead of reimplementing the format integration.
Custom and third-party types
An arbitrary class is not serialized by guessing its __dict__. Choose an explicit extension mechanism:
Use SerializableType when you own a class and want the class itself to define its stable serialized representation. The contract is explicit: _serialize() returns the representation, and _deserialize() rebuilds an instance.
For a third-party class you cannot modify, use SerializationStrategy. For a representation that changes by external API, prefer a Dialect so the domain type stays independent of one wire contract.
Without annotation processing, the value passed to _deserialize() is raw input and the value returned from _serialize() is accepted as the final basic representation. This is ideal when your methods perform the entire conversion themselves.
Annotation-aware conversion
Set use_annotations=True when Mashumaro should recursively convert the function annotations on the input of _deserialize() and the return of _serialize().
Both annotations matter. The _deserialize() parameter tells Mashumaro how to unpack raw data before your method runs. The _serialize() return type tells it how to pack the value your method returns.
On Python 3.12+, the class header may be written as class DictWrapper[K, V](dict[K, V], SerializableType, use_annotations=True):.
GenericSerializableType
GenericSerializableType is a lower-level alternative. Instead of having Mashumaro substitute method annotations, your methods receive a list of concrete type arguments.
python
fromdatetimeimportdatefromtypingimportGeneric, TypeVarfrommashumaro.typesimportGenericSerializableTypeT=TypeVar("T")
classLegacyBox(Generic[T], GenericSerializableType):
def__init__(self, value):
self.value= value
def_serialize(self, types):
item_type = types[0]
if item_type is date:
returnself.value.isoformat()
returnself.value@classmethoddef_deserialize(cls, value, types):
item_type = types[0]
if item_type is date:
value = date.fromisoformat(value)
returncls(value)
Prefer annotation-aware SerializableType for new code: it composes naturally with nested generic types and gives Mashumaro enough information for JSON Schema generation. Use GenericSerializableType when the concrete type objects themselves drive custom runtime logic.
Dataclass types that implement the interface
A class may be both a dataclass and a SerializableType. Its explicit interface wins over ordinary dataclass field packing for uses of that type. This is useful when the in-memory field layout must not leak into the wire representation.
For annotation-aware classes, JSON Schema follows the _serialize() return annotation. Without a usable return annotation, the schema builder cannot reliably infer the external representation. This is one reason to annotate custom serialization even when runtime conversion would work without it.
Design guidance
Keep _serialize() deterministic and side-effect free.
Make _deserialize() accept only the documented external shape.
Version the representation deliberately; changing a list to a mapping is a wire breaking change.
Use annotation-aware conversion when nested values should follow normal Mashumaro rules.
Test round trips and exact basic output, not only object equality.
Use a strategy or dialect instead when the representation belongs to a particular API rather than to the class itself.
A SerializationStrategy adds or overrides conversion for a type without modifying that type. It is the main extension point for third-party classes, alternate scalar formats, and reusable organization-wide representation rules.
Strategies can be attached to one field, registered by type in a model Config, or packaged in a switchable dialect.
The strategy instance can hold configuration, which makes one implementation reusable for multiple fields. Python documents the directives accepted by strftime() and strptime() in its format-code reference.
Register by type in Config
Register a strategy once when every field of a type should use it:
The mapping key is the target Python type. A field-level serialization_strategy is the more local rule and should be used when one field differs from the model convention.
Dictionary form
You do not have to define a strategy class. A config or dialect entry may contain serialize and deserialize callables:
Entries may define only one direction. The missing direction falls back to normal handling where that is meaningful. The UUID.hex attribute used above produces the 32-character form without hyphens. For a stable round-trip contract, define and test both directions.
Annotation-aware strategies
With use_annotations=True, Mashumaro converts according to the deserialize() parameter annotation before calling the method, and converts the serialize() result according to its return annotation afterward.
As a result, an input string such as "1723800000" is converted to float before deserialize() runs. The return annotation also lets JSON Schema describe the serialized type as a number.
Annotation processing changes the boundary of your method. Without it, the method sees raw input and its return value is final. With it, Mashumaro applies recursive typed conversion on both sides.
Match subclasses
By default a strategy registered for Base matches exactly Base. Opt into subclass matching when a base-class policy should apply across a hierarchy:
When more than one registered base type matches, Mashumaro follows the target type's method-resolution order and uses the first registered match. Register narrow rules when multiple hierarchies could overlap.
Deserialization by a generic base such as Enum cannot know the concrete subclass from only the base strategy method. The generated field still knows Color, but the example intentionally leaves deserialization undefined to emphasize that each direction needs a real policy.
Generic third-party types
A strategy can itself be generic. Register it under the target origin type; Mashumaro substitutes the field's concrete type arguments into the strategy annotations. The example uses the third-party multidict.MultiDict container.
Generic strategies use their annotations implicitly; there is no need to pass use_annotations=True. The number and order of strategy type variables must match the target generic type.
Built-in RoundedDecimal
RoundedDecimal(places=None, rounding=None) serializes a Decimal to a string and optionally quantizes it first. Deserialization constructs a Decimal from the incoming value.
It is safe only when the next layer accepts the raw value. MessagePack can accept bytes; standard JSON cannot encode an arbitrary object. Pass-through also skips normal reconstruction, so use it deliberately at trusted boundaries.
Strategies and dialects
Put a strategy in a dialect when the same Python model needs different external representations:
See Dialects for default, call-time, and codec usage.
Precedence and design rules
A field-specific strategy is the narrowest and clearest override.
A model config expresses one model family's stable default.
A dialect expresses an external representation that may be selected or reused.
Built-in type behavior is used only when no applicable override replaces it.
Method annotations affect annotation-aware runtime conversion and JSON Schema output.
Avoid strategies that depend on unrelated global state. Immutable strategy objects with explicit constructor settings are easier to cache, test, and reason about.
Field options customize one dataclass field. They live in dataclasses.field(metadata=...), so they compose with defaults, factories, init, repr, and metadata used by other libraries. You provide the metadata mapping when declaring the field; after the field is created, dataclasses exposes it as a read-only mapping intended for third-party extensions. Mashumaro reads its options from that mapping.
Use field options for a local exception. If all fields of a type share a rule, move it to Config Options or a Dialect.
Add a return annotation when JSON Schema should reflect the overridden representation. Here the field schema becomes an integer rather than the default date-time string.
Exceptions raised by field conversion are reported as InvalidFieldValue with the field name, annotated type, input value, and holder class. See Errors and Troubleshooting.
Serialization engines
String engine names select optimized built-in behavior for a small set of types.
Named tuple engines
For a NamedTuple field, both directions accept as_list and as_dict:
This changes serialization only. Deserialization behavior still follows the field definition, alias, and default. Use a default when omitted input must still construct the dataclass.
Datetime parser engines
The deserialize option supports ciso8601 and pendulum for datetime, date, and time fields:
Install the named third-party module yourself. If it is missing, Mashumaro raises ThirdPartyModuleNotFoundError identifying the dependency and field. These parser engines affect only deserialization; choose a matching serializer if you need a non-default output format.
Per-field SerializationStrategy
A strategy keeps both directions and constructor options in one object:
Aliases are used for input by default. Output keeps Python field names unless serialize_by_alias=True, a dynamic by_alias=True flag is enabled, or a dialect selects alias output.
Aliases are tried in declaration order. If an input contains more than one of them, the first matching alias wins. The first alias is also the primary alias used when serializing by alias and as the property name in generated JSON Schema. An empty sequence uses the Python field name.
Set allow_deserialization_not_by_alias=True when the Python field name should also be accepted. It is tried after all declared aliases. With forbid_extra_keys=True, every declared alias and the optional Python field name count as expected keys.
Annotated aliases
Aliases can stay next to the type with Annotated instead of in field() metadata. Repeat Alias(...) to declare multiple names; the first one is primary:
Pass-through does not make a value serializable by the final format. A raw custom object may be acceptable to a database adapter but will still fail in json.dumps. It also bypasses input conversion, so it should be used only where the caller already guarantees the runtime type.
Defaults and omission
Field defaults are regular dataclass defaults and default factories. During deserialization, a missing input key uses default or default_factory; a required field without either raises MissingField.
Omission options have different meanings:
Feature
Condition
Scope
serialize="omit"
Always
One field
omit_none
Current value is None
Config, dialect, or call flag
omit_default
Current value equals its default/factory result
Config or dialect
Do not use omission to conceal a required external field. A receiver still needs a compatible default or migration rule.
Common mistakes
Defining only a serializer and expecting the new representation to round-trip.
Using an alias and assuming output changes automatically.
Using pass_through before a format encoder that cannot accept the object.
Forgetting a return annotation when JSON Schema should see the overridden type.
Selecting ciso8601 or pendulum without installing it.
Hiding a secret only with a post-serialization hook when serialize="omit" would make the policy explicit at the field.
An inner Config class defines model-wide serialization behavior. Configuration follows normal Python class inheritance, so a project can create a base mixin and let individual dataclasses override only their exceptions.
Inheriting BaseConfig is recommended for discoverability, type checking, and documented defaults, though a plain nested class with recognized attributes also works.
Complete option reference
Option
Default
Effect
debug
False
Print generated source code
code_generation_options
[]
Add optional method parameters/features
serialization_strategy
{}
Type-to-strategy mapping
aliases
{}
Field names mapped to one alias or ordered aliases
serialize_by_alias
Unset
Emit aliases by default
allow_deserialization_not_by_alias
False
Accept Python names for aliased fields
omit_none
Unset
Omit fields whose value is None
omit_default
Unset
Omit fields equal to defaults
namedtuple_as_dict
Unset
Represent named tuples as mappings
allow_postponed_evaluation
True
Defer compilation when a type reference is unresolved
dialect
None
Fixed default serialization dialect
orjson_options
0
Default orjson option bitmask
json_schema
{}
Dataclass-level JSON Schema overrides
discriminator
None
Polymorphic subtype selection
lazy_compilation
False
Compile generated methods on first use
sort_keys
False
Sort serialized dictionary keys
forbid_extra_keys
False
Reject unexpected input keys
“Unset” is an internal sentinel rather than False, allowing a format dialect to provide a default while an explicit model value can override it.
debug
Set debug = True to print the generated packing and unpacking source. This is useful when investigating configuration precedence, a performance issue, or an unexpected conversion.
python
classConfig(BaseConfig):
debug =True
Generated source is an implementation detail. Use it for diagnosis, not as an API to copy or patch.
code_generation_options
Optional features change generated method signatures and are therefore opt-in:
Each value may be a string or an ordered sequence of strings. During deserialization, aliases are tried in order and the first key present in the input wins. The first alias is the primary name used for alias serialization and generated JSON Schema. An empty sequence uses the Python field name.
A per-field metadata alias overrides an Annotated alias, which overrides this mapping. Alias sources are not combined.
serialize_by_alias
Aliases are input names by default. Set serialize_by_alias = True to use them for output too:
This is valuable for gradual API migrations. With multiple aliases, the Python field name is tried after every declared alias. If several accepted keys are present, the first declared alias wins.
Default factories are evaluated to determine the comparison value. Keep them cheap and deterministic.
namedtuple_as_dict
Set this option to represent every named tuple field as a mapping instead of the default list. A field-level as_list or as_dict engine can override the model-wide choice.
python
classConfig(BaseConfig):
namedtuple_as_dict =True
Dictionary representation is more self-describing and tolerates omitted defaulted items; list representation is more compact.
Forward references
allow_postponed_evaluation
The default True lets Mashumaro postpone generated-method compilation when a referenced type is not defined yet. This supports string annotations, from __future__ import annotations, mutually recursive models, and Python 3.14's deferred annotation behavior.
With allow_postponed_evaluation = False, an unresolved type raises UnresolvedTypeReferenceError during class processing instead of deferring. Disable it when eager failure is more valuable than declaration-order flexibility.
At least one of include_subtypes or include_supertypes must be enabled. See Discriminator.
Compilation controls
lazy_compilation
With the default False, mixin methods are normally generated during class creation. Set True to defer work until the first serialization/deserialization call:
python
classConfig(BaseConfig):
lazy_compilation =True
This can reduce import time in applications that define many models but use only a subset. The first call pays the compilation cost; subsequent calls use the generated method.
References captured before the first call need extra care because they keep pointing to the lazy placeholder after the generated method is installed on the class. See Eager versus lazy mixins for safe ways to retain a callable.
sort_keys
Sort keys in each generated dictionary:
python
classConfig(BaseConfig):
sort_keys =True
This fixes the generated field order when the serialization method is compiled, so it does not sort the output dictionary on every call. Nested dataclasses use their own config. Separately from mashumaro, a format library may sort arbitrary mapping keys at runtime; for example, json.dumps(..., sort_keys=True) and orjson.OPT_SORT_KEYS are independent of this option.
Strict input with forbid_extra_keys
The default ignores input keys that do not correspond to fields. Set strict mode to raise ExtraKeysError:
A dialect is a reusable serialization profile. It separates an external representation from the data model and can be selected as a model default, passed at call time, or supplied to a codec.
Typical uses include public versus legacy API shapes, database versus network representations, date formats by partner, compact versus readable named tuples, and format-native pass-through rules.
If the model also defines Config.dialect, that class is the default when the call omits dialect=; the explicit call argument selects another profile.
Dynamic dialect support propagates through supported nested dataclasses, unions, typed dictionaries, named tuples, and generic shapes when their generated methods participate in dialect handling.
Codec default dialect
Every reusable encoder and decoder accepts default_dialect=:
Passes bytes/bytearray/memoryview/Buffer in MessagePack binary form, avoids copying lists/dicts
When a TOML, MessagePack, or orjson codec receives default_dialect=YourDialect, the format dialect is merged with your custom dialect so required native behavior remains available.
Merging dialects
Dialect.merge(other) creates a new dialect class. Serialization-strategy dictionaries are copied and overlaid; other wins for the same type/direction. This resembles a dictionary union for strategy entries. omit_none, omit_default, and no_copy_collections also take the explicit value from other, falling back to the left-hand dialect when unset.
Current merge behavior is intentionally narrow: serialize_by_alias and namedtuple_as_dict are not copied by Dialect.merge. If a merged profile needs them, define them on the final dialect class explicitly.
It can reduce allocations, but it changes isolation guarantees: a downstream encoder or hook may see the caller's mutable collection. Use it only for trusted internal pipelines and benchmark the actual workload. Built-in format dialects use it where their encoders can safely consume the native containers.
Config versus dialect
Model config describes the model's default policy; a dialect describes a reusable external profile. An explicit model value can override a dialect default for options such as omission because “unset” and False are distinct.
Choose one source of truth per concern:
Put invariant security behavior such as forbid_extra_keys in config; dialects do not define it.
Put alias-output policy, omission, and type representations in a dialect when they vary by destination.
Put an unchanging representation in config when the model has only one contract.
Put a one-field exception in field metadata.
Keep invariant security behavior in model config. A caller-selectable dialect should not be able to weaken an input-validation boundary.
Failure modes
Passing a dialect instance instead of a class raises BadDialect.
Passing dialect= without ADD_DIALECT_SUPPORT raises TypeError because the generated method has no such parameter.
Pass-through rules inherited from a format dialect still depend on the final encoder's capabilities.
A custom dialect used for serialization must define a compatible reverse rule if round trips are required.
Aggressive no_copy_collections can leak mutations across a serialization boundary.
Discriminator chooses the concrete dataclass variant when deserializing a union or class hierarchy. A tagged discriminator performs direct lookup and gives the wire format an explicit compatibility contract; an untagged discriminator tries candidate shapes in sequence.
Parameters
Parameter
Default
Meaning
field
None
Input key containing the variant tag
include_subtypes
False
Include descendants of annotated/configured classes
include_supertypes
False
Include the listed/annotated classes as fallback variants
variant_tagger_fn
None
Compute one tag or a list of tags from a variant class
At least one of include_subtypes or include_supertypes must be true. Otherwise construction raises ValueError.
Tagged class hierarchy
Use Annotated when only one field needs polymorphic behavior:
The tag can come from a class attribute or a field declared with ClassVar, Final, Literal, or a string enum. Literal and StrEnum fields are especially convenient because the tag is also naturally included in serialization.
By default, only Final, Literal, and StrEnum tag fields are processed during serialization. An unannotated class attribute or ClassVar can select a variant during deserialization, but it is not emitted as a serialized field.
Class-level discriminator
Put the discriminator in the base model's Config when every use of that base class should deserialize polymorphically:
This works for nested Event fields and direct Event.from_dict() calls.
If an intermediate descendant should also dispatch among its own descendants, opt in explicitly by defining its own Config. An empty subclass is sufficient to reuse the parent configuration:
Do not set include_supertypes=True on a class-level discriminator: selecting the configured base as its own fallback would recurse. Use Annotated on a union/field when supertypes are needed.
Discriminated unions
Use a union when variants do not share a useful base class or only a subset of a hierarchy is legal:
For a tagged union, variant selection is direct rather than “try every union branch”. This is faster and avoids accidental success when models have overlapping fields.
Untagged shape matching
Omit field when you have to read an existing untagged format whose variants are distinguishable by their required fields. For example, a legacy alerting configuration might identify notification targets only by their shape:
Shape matching also works with explicitly listed union variants. This is useful when the listed classes have descendants that should take priority over their parents:
With a plain EmailTarget | WebhookTarget, EmailTarget is attempted first and can accept this payload without preserving template. The discriminator discovers TemplatedEmailTarget through include_subtypes=True and attempts it before the explicitly listed parent classes. include_supertypes=True keeps those listed classes as fallback candidates.
email and url make the two shapes distinct. Mashumaro attempts candidate variants until one deserializes, so this is slower than tag lookup and can be ambiguous when classes have similar required fields. Use an explicit channel tag whenever you control the contract.
Fallback to a supertype
Enable both directions on an Annotated discriminator to prefer a known subtype and fall back to the base model for a forward-compatible payload:
Subtypes are attempted first; supertypes are attempted afterward. An input with only {"name": "manual escalation"} can therefore become NotificationTarget("manual escalation"), while a payload containing email still selects EmailTarget.
Custom tag generation
variant_tagger_fn receives each variant class and returns its tag:
Return a list to accept multiple tags for one variant during migrations:
python
defcompatible_tags(cls):
current =cls.__name__.removesuffix("Event").lower()
return [current, f"v1:{current}"]
Keep tag generation deterministic and collision-free. A tagger maps classes to accepted input tags; it does not automatically inject a missing tag into serialized output. Add a field, hook, or explicit strategy when output must contain it.
Error behavior
Situation
Exception
Configured tag key is missing
MissingDiscriminatorError
Tag has no registered variant
SuitableVariantNotFoundError
Untagged candidates all fail inside a field
Usually wrapped as InvalidFieldValue
Neither subtype nor supertype inclusion enabled
ValueError when creating Discriminator
Catch these exceptions at an input boundary to produce a stable API error. Their attributes expose the field/tag or variant information; see Errors and Troubleshooting.
Best practices
Prefer a Literal or StrEnum dataclass field for a self-serializing tag.
Keep tag values stable even if Python class names change.
Use Annotated for local union behavior and Config for hierarchy-wide behavior.
Avoid untagged matching when variants overlap or the list may grow large.
Test missing, unknown, legacy, and duplicate tags explicitly.
Mashumaro keeps common generated methods small. Features that add runtime branches or method parameters are enabled through Config.code_generation_options.
Available flags
Constant
Generated behavior
TO_DICT_ADD_OMIT_NONE_FLAG
Adds omit_none= to serialization methods
TO_DICT_ADD_BY_ALIAS_FLAG
Adds by_alias= to serialization methods
ADD_DIALECT_SUPPORT
Adds dialect= to serialization and deserialization methods
ADD_SERIALIZATION_CONTEXT adds a caller-defined context value and passes it to __pre_serialize__ and __post_serialize__ hooks that accept the extra parameter. Any is convenient for open-ended examples, but applications can annotate a TypedDict, protocol, or mapping for a stricter context contract.
Context is serialization-only. Deserialization hooks do not receive it. The context type and mutation policy belong to your application; a small immutable Mapping is often easiest to reason about.
Outer.value is omitted, while Inner.value remains because Inner did not opt into the dynamic flag. Add the option to a shared base config when an entire model graph should honor it.
The same principle prevents a parent call from silently changing a nested model that intentionally has a different external contract.
Performance guidance
Each option adds generated code and sometimes a branch per call. The cost is usually small, but do not enable every option globally without a use case. Fixed Config or dialect values produce a simpler API when behavior never changes at runtime.
Mashumaro resolves type variables through generic dataclass inheritance, parameterized fields, codecs, serialization strategies, and custom serializable types. It supports classic Generic, PEP 646 variadic generics, PEP 695 syntax and type aliases, PEP 696 TypeVar defaults, and recursive generic models.
Parameterized field types
The most common pattern is a generic dataclass used with different concrete arguments:
The generic dataclass itself does not need a mixin when it is nested. For direct typed methods, create a concrete subclass such as DateBox(Box[date], DataClassDictMixin); calling an origin method through Box[date] does not specialize that generated method at runtime.
Concrete generic inheritance
Subclass a generic dataclass to create a named concrete model:
Partial specialization and replacing a parent TypeVar with another TypeVar are supported. Unresolved variables use a bound, constraints, a default, or Any depending on the declaration.
On Python 3.11+, star-unpacking syntax such as tuple[*Ts] is also available. Mashumaro supports fixed items around an unpack, arbitrary-length tuple arguments, and empty variadic tuples where Python's type syntax permits them.
PEP 695 syntax (Python 3.12+)
Python 3.12 can declare type parameters directly:
python
fromdataclassesimportdataclass@dataclassclassBox[T]:
value: T@dataclassclassPair[K, V]:
key: K
value: V
These classes work in parameterized fields, concrete inheritance, codecs, annotation-aware SerializableType, and generic strategies just like classic Generic classes.
The grammar is Python 3.12-only; a library supporting Python 3.10/3.11 should keep PEP 695 declarations in version-specific modules or use classic syntax.
PEP 695 type aliases
The type statement can define simple, generic, and recursive shape aliases:
Mashumaro resolves parameterized aliases and guards direct, wrapped, and mutually recursive aliases from infinite recursion. JSON Schema definitions use stable alias names.
Recursive generic models
Forward references and Self work with generic dataclasses:
For a generic class you own, SerializableType(use_annotations=True) substitutes concrete type arguments into _serialize() and _deserialize() annotations. This is usually more concise than inspecting type objects manually.
Construct and reuse one codec per concrete shape. A bare generic with unresolved parameters has less conversion information and may fall back to bounds/defaults/Any.
Version matrix
Capability
Native starting in
Earlier supported versions
Classic Generic[T]
Python 3.10
Not applicable
TypeVarTuple and Unpack objects
Python 3.11
Import them from typing_extensions on 3.10
tuple[*Ts] syntax
Python 3.11
Use tuple[Unpack[Ts]] on 3.10
PEP 695 class and type-alias syntax
Python 3.12
Use classic Generic declarations and assignment-style aliases
TypeVar defaults
Python 3.13
Import TypeVar from typing_extensions
Deferred annotation evaluation
Python 3.14
Use the future-annotations model with allow_postponed_evaluation
Generic slice annotations
Python 3.15
Use unparameterized slice
Troubleshooting generics
Parameterize the root shape: prefer Box[date] over bare Box.
Keep strategy TypeVars aligned with the third-party generic's parameter order.
Use typing_extensions imports for a source file shared with Python 3.10.
Put Python 3.12-only grammar in a module that older interpreters never parse.
Leave allow_postponed_evaluation=True for forward and recursive references.
Inspect generated code with Config.debug=True when a TypeVar resolves to an unexpected fallback.
Hooks intercept a dataclass at four points around generated conversion. They work through dictionary and format mixins and when the dataclass is nested inside a codec shape.
With serialization context enabled, the two serialization hooks may also accept context.
Before deserialization
Normalize or migrate raw keys before field lookup:
python
fromdataclassesimportdataclassfromtypingimportAnyfrommashumaro.mixins.jsonimportDataClassJSONMixin@dataclassclassUser(DataClassJSONMixin):
name: str
age: int@classmethoddef__pre_deserialize__(
cls, data: dict[str, Any]
) ->dict[str, Any]:
normalized = {key.lower(): value for key, value in data.items()}
if"years"in normalized and"age"notin normalized:
normalized["age"] = normalized.pop("years")
return normalized
assertUser.from_json('{"NAME": "Alice", "years": "30"}') ==User(
"Alice", 30
)
The hook runs after the format parser has produced a Mapping but before typed field conversion. It can therefore rename keys and reshape input while leaving date/enum/nested conversion to Mashumaro.
This hook receives an instance whose fields already have their annotated runtime types. Returning an instance is mandatory; it may be the same object or a replacement compatible with the class contract.
Before serialization
Select or prepare the object before generated field packing:
python
fromdataclassesimportdataclassfromtypingimportClassVarfrommashumaroimportDataClassDictMixin@dataclassclassMetered(DataClassDictMixin):
value: int
serializations: ClassVar[int] =0def__pre_serialize__(self) ->"Metered":
type(self).serializations += 1returnself
ClassVar keeps the counter out of the dataclass field set. Avoid mutating ordinary instance fields just to serialize them: repeated calls should usually produce the same result. A strategy or __post_serialize__ transformation is easier to reason about for pure representation changes.
After serialization
Transform the complete dictionary after all fields have been packed:
For a field that must never be emitted, serialize="omit" is more explicit. A post hook is best for transformations that need multiple fields or the complete mapping.
Serialization context
Enable ADD_SERIALIZATION_CONTEXT to make per-call policy available to serialization hooks:
Context propagates through nested models whose generated methods support it. Models without the option keep their ordinary hook signature and behavior.
Hook inheritance
Hooks can be defined in a parent class and are discovered on descendants:
python
classLowercaseInput:
@classmethoddef__pre_deserialize__(cls, data):
return {key.lower(): value for key, value in data.items()}
@dataclassclassModel(LowercaseInput, DataClassDictMixin):
value: int
This is useful for cross-cutting migrations, but a base hook affects every descendant. Keep it generic and test inheritance combinations.
Signature validation
Mashumaro validates whether pre/post deserialization hooks are classmethods and whether hook signatures match the enabled features. Invalid definitions raise BadHookSignature during class processing instead of failing much later in a request.
With context, add an optional second argument to pre-serialize and third argument to post-serialize as shown above.
Hooks versus other extensions
Requirement
Prefer
Convert one field
Field callable or strategy
Convert every value of a type
Config strategy
Switch representation by destination
Dialect
Rename one key
Alias
Migrate or reshape a whole old payload
Pre-deserialize hook
Add/remove output based on several fields
Post-serialize hook
Choose a polymorphic class
Discriminator
Hooks are powerful because they see broad state. Use the narrowest extension that expresses the rule; narrow rules produce better schemas and fewer surprising interactions.
Mashumaro builds JSON Schema for any supported type shape, not only dataclasses. The result is a typed JSONSchema object with to_dict() and to_json() methods.
Dataclass fields without defaults are required. Defaults are serialized into the schema using the model's serialization rules. Aliases become property names. When a field has multiple aliases, JSON Schema uses the first (primary) alias, including aliases declared by repeating Alias(...) in Annotated:
Set with_definitions=False when embedding the returned fragment in a larger document that stores definitions elsewhere:
python
fragment = build_json_schema(
list[User],
dialect=OPEN_API_3_1,
with_definitions=False,
)
Override the reference root when the destination uses another component location:
python
fragment = build_json_schema(
list[User],
all_refs=True,
with_definitions=False,
ref_prefix="#/components/responses",
)
Trailing slashes are normalized. Recursive dataclasses and recursive type aliases force definitions/references as needed even when all_refs=False, preventing infinite inline expansion.
Incremental JSONSchemaBuilder
Use a builder when assembling a larger OpenAPI or schema document from many models:
The builder shares one Context, so definitions accumulate across calls. Constructor options are dialect, all_refs, ref_prefix, and plugins.
Type-to-schema mapping
Python shape
Schema shape
str
{"type": "string"}
int
{"type": "integer"}
float
{"type": "number"}
Decimal, Fraction
String with Mashumaro's decimal/fraction format extension
bool
{"type": "boolean"}
None
{"type": "null"}
Any
Empty unrestricted schema
Literal[...], Enum
enum values
A | B
anyOf
list[T], sequences
Array with items
Fixed tuple[...]
Array with prefixItems, minItems, maxItems
set[T], frozenset[T]
Array with uniqueItems
Mapping
Object with propertyNames/additionalProperties
TypedDict
Object properties, required keys, and extra-item policy
Dataclass
Closed object with properties and required keys
datetime, date, time
String with standard format
UUID, IP addresses
String with standard format
slice, slice[...]
Fixed array of three component schemas ([start, stop, step]); components also allow null
bytes, bytearray, memoryview, Buffer
String with Mashumaro's base64 format extension
Paths
String with Mashumaro's path format extension
Mashumaro also defines extension formats for time zones, timedelta, networks, IP interfaces, decimal, fraction, and Base64 where the standard JSON Schema format vocabulary has no exact built-in format.
For a bare slice, all three component schemas are unrestricted. Generic slice arguments determine the schemas for start, stop, and step.
TypedDict extra items
PEP 728 controls whether a TypedDict accepts keys that are not declared in its class body. Mashumaro maps its options to JSON Schema's additionalProperties keyword:
TypedDict declaration
Generated additionalProperties
closed=True
false
closed=False
true
extra_items=T
The generated schema for T
Use either closed or extra_items, not both. Starting with Python 3.15, these options are available on typing.TypedDict out of the box. On earlier Python versions, import TypedDict from typing_extensions instead. The example below uses the backport for compatibility with older Python versions:
Overlay values are applied after automatic type generation and combine with constraint annotations. If multiple overlays set the same attribute, the last one wins. Explicit None for const and default is preserved.
An additionalProperties override may be True, False, or a JSONSchema object. A property override replaces that field's automatically generated schema; an Annotated overlay modifies the generated schema. Prefer overlays for additive field customization.
Custom serialization and schema output
Schema describes the serialized representation, not only the Python annotation. Give custom serializers return annotations so Mashumaro can follow the output type:
The schema for name becomes an array of strings and count becomes a string. If a callable lacks a return annotation or the annotation cannot be resolved, the builder warns and falls back to an unrestricted schema.
The same principle applies to annotation-aware SerializableType and SerializationStrategy methods.
Plugins
Plugins can add an unsupported type or modify an existing schema. They run in order for every instance in the shape.
The built-in docstring plugin adds Python class docstrings from dataclasses:
Returning a schema replaces/continues the current result. Returning None or raising NotImplementedError means the plugin does not handle that instance. A plugin may receive schema=None for a type unsupported by built-in creators and can return a complete schema to add support.
Register the same plugin list on build_json_schema(..., plugins=[...]) or JSONSchemaBuilder(plugins=[...]).
Recursive models and aliases
Direct recursion, mutual recursion, typing.Self, and PEP 695 recursive aliases are supported. The builder creates definitions automatically when it encounters a cycle:
python
from__future__importannotationsfromdataclassesimportdataclass@dataclassclassNode:
value: int
children: list[Node]
schema = build_json_schema(Node)
assert"$defs"in schema.to_dict()
Practical checklist
Build schemas from the same concrete shape passed to your codec.
Use aliases consistently so schema property names match actual output.
Annotate custom serializer return types.
Choose Draft 2020-12 or OpenAPI 3.1 before deciding reference layout.
Use JSONSchemaBuilder when assembling multiple components.
Prefer Annotated constraints and overlays for field-local additions.
Snapshot or structurally test generated schemas as part of API compatibility checks.
Remember that JSON Schema documents the serialized contract; it does not execute Mashumaro deserialization.
Mashumaro raises specific exceptions for schema construction, input conversion, hooks, dialects, and optional integrations. Catch narrow exceptions at external boundaries and keep programming/configuration errors visible during development.
A dataclass default or default_factory makes missing input legal. Optional[T] does not automatically make a field optional in the mapping; it only allows None. Give it a default when omission is valid:
python
nickname: str|None=None
For an alias, the alias is the required input key unless allow_deserialization_not_by_alias=True.
Strict mode is best at public boundaries. During a rolling migration, normalize legacy keys in __pre_deserialize__ before strict checking or explicitly allow both an alias and the Python name.
Invalid values
InvalidFieldValue wraps a failure to construct the annotated type:
Inspect exc.msg and the chained exception when available. The most common causes are a wrong wire representation, a serializer/deserializer pair that disagrees, an invalid literal/enum value, or an ambiguous union.
Do not catch every ValueError around a full payload. Catch InvalidFieldValue and report the structured field context.
Unsupported fields and engines
An arbitrary class is not serialized from __dict__ automatically. This example uses the standard library's queue.Queue:
Depending on compilation mode, defining or first using this class raises UnserializableField. Add an explicit SerializableType, strategy, field callable, or pass-through rule appropriate to the final format.
Engine names are type-specific. as_dict/as_list apply to named tuples; ciso8601/pendulum apply to date/time types; omit is serialization-only. A typo or incompatible engine raises UnsupportedSerializationEngine or UnsupportedDeserializationEngine.
Missing optional parser modules
Selecting deserialize="ciso8601" or deserialize="pendulum" does not install ciso8601 or pendulum. Missing modules raise ThirdPartyModuleNotFoundError with:
Import the referenced type before the model is first used.
Avoid references hidden only under TYPE_CHECKING when runtime evaluation needs them.
For local-scope models, ensure the referenced object remains resolvable in the expected namespace.
Use Config.debug=True to see when compilation happens and what concrete type was resolved.
Discriminator failures
MissingDiscriminatorError.field_name identifies an absent tag key. SuitableVariantNotFoundError exposes:
variants_type
discriminator_name
discriminator_value
If a valid subclass is ignored, check that the tag attribute is defined directly on that concrete descendant, not only inherited. If a custom variant_tagger_fn is used, verify that it returns stable unique values and that the input tag has the same basic type.
For untagged variants, enable forbid_extra_keys where suitable and make required fields sufficiently distinct; otherwise a broader class may succeed before the intended one.
Hook failures
The two deserialization hooks must be classmethods:
Serialization hooks are instance methods. Context parameters are legal only with ADD_SERIALIZATION_CONTEXT. A wrong signature raises BadHookSignature during code generation.
Dialect failures
Pass a dialect class:
python
model.to_dict(dialect=PublicAPIDialect)
Do not pass PublicAPIDialect(). Also enable ADD_DIALECT_SUPPORT before using the call-time keyword. A fixed Config.dialect does not require the flag.
If a merged dialect appears to lose alias or named-tuple behavior, remember that Dialect.merge() currently merges strategies, omission flags, and no_copy_collections; define serialize_by_alias and namedtuple_as_dict explicitly on the final class.
“Object is not JSON serializable”
This message usually comes from json.dumps, after Mashumaro's typed stage. Typical causes:
pass_through left a custom object in the basic form.
A custom serializer returned a non-JSON value.
A dictionary has unsupported key types.
A codec transform returned the wrong layer.
A format-native dialect was reused with a different encoder.
Inspect obj.to_dict() or a BasicEncoder result first. If the unsupported object is already present there, fix the strategy. If the basic form is JSON-safe, inspect the custom JSON encoder.
Why did my alias not appear in output?
Aliases are used for input by default. Enable one of:
Config.serialize_by_alias = True
TO_DICT_ADD_BY_ALIAS_FLAG, then to_dict(by_alias=True)
A dialect with serialize_by_alias = True
The alias may come from field metadata, Annotated[..., Alias(...)], or Config.aliases in that precedence order.
Why was None not omitted in a nested model?
A dynamic omit_none=True flag only propagates through nested converters that support the option. Put TO_DICT_ADD_OMIT_NONE_FLAG on a shared base config or use fixed omit_none=True on the nested model/dialect.
Why is TOML different from to_dict()?
TOML has no null and has native date/time types. Its built-in dialect omits None and passes date/time objects through to tomli-w. This is deliberate format behavior, not a failed basic round trip.
Debugging workflow
Reduce the failure to to_dict()/from_dict() or a Basic codec first.
Print the exact annotated shape passed to the codec.
Enable Config.debug=True to inspect generated code.
Check field metadata, then model config, then active dialect.
Verify custom serialize/deserialize functions are true inverses.
Test the final format's constraints independently.
Reproduce under the oldest and newest supported Python versions if annotations are involved.
Turn the reproduction into a focused test before changing configuration.
Boundary error handling
At an HTTP or message-consumer boundary, a useful pattern is:
Treat configuration exceptions such as UnserializableField, BadHookSignature, and BadDialect as deployment/programming failures rather than malformed user input.
These patterns combine the lower-level features into common application contracts. Each recipe keeps typed conversion, wire representation, and compatibility policy explicit.
Camel-case API fields
For a small stable model, declare aliases directly:
Mashumaro deliberately uses an explicit alias mapping rather than guessing a naming convention. For a generated model set, create the Config.aliases dictionaries in your model-generation layer and snapshot-test them.
Rename a field without breaking old payloads
Declare the current wire name first, followed by every historical input name. Enable allow_deserialization_not_by_alias when the Python field name should also remain valid during the migration:
The first alias is the canonical output name. Later aliases are accepted only on input, so compatibility can be wider for readers than for writers.
Give logical types distinct wire contracts
When several values share the same runtime type but have different wire contracts, give each one a logical type and register its own strategy. NewType gives type checkers the strongest separation, while Annotated adds no wrapper call when values are constructed manually:
The type statement creates a TypeAliasType and requires Python 3.12 or newer. On Python 3.10 and 3.11, import TypeAliasType from typing_extensions and write DeviceID = TypeAliasType("DeviceID", str) instead. Keep the modern syntax in a version-specific module when the same source tree must be parsed by older Python versions.
Strict public payload, flexible internal model
Use a boundary wrapper with strict keys while keeping reusable nested domain objects permissive:
Strictness on the root catches request typos. Add strict config to nested types too if unknown nested fields must be rejected.
Partial updates with TypedDict
A dataclass describes a constructible object and normally requires fields without defaults. A non-total TypedDict is a better shape for PATCH-style data:
For a field that must never leave the process, prefer serialize="omit". Context redaction is appropriate only when multiple explicitly tested audiences are a real requirement.
Versioned event envelopes
Keep protocol versioning separate from variant tagging:
The Literal version selects an envelope migration; the discriminator selects an event class. Keep tag values stable and add a new version when representation meaning changes incompatibly.
The callable receives Mashumaro's basic form, so normal typed conversion is preserved.
Combine a format mixin with SerializableType
A dataclass can use a format mixin for convenient top-level methods while implementing SerializableType to define a compact representation when it is nested in another model:
The mixin methods serialize Coordinate as a top-level dataclass document. When a Coordinate is used as a field, its explicit SerializableType contract wins over ordinary dataclass field packing. This combination is useful when a reusable type needs standalone format helpers and a stable embedded representation. If the compact form belongs to only one external API rather than to the type itself, prefer a serialization strategy or dialect.
Rounding at serialization does not mutate the in-memory Decimal. The ROUND_HALF_EVEN mode resolves halfway cases toward the nearest even value. Decide whether deserialized values must already have the same scale and validate that rule separately.
Codec adapter around an existing parser
If a framework already parses the transport, use a Basic codec:
This avoids encoding JSON to text and parsing it again just to reach the typed conversion layer.
Compatibility tests
For a durable contract, test exact representations in both directions:
python
deftest_user_wire_contract():
wire = {"userId": 42, "displayName": "Alice"}
model =APIUser(42, "Alice")
assertAPIUser.from_dict(wire) == model
assert model.to_dict() == wire
Add fixtures from previous released versions, unknown keys/tags, missing optional fields, and boundary numeric/date values. Round-trip equality alone can miss an unintended but symmetrical wire-format change.
Mashumaro generates specialized Python functions for a concrete type shape. After compilation, conversion runs through direct code tailored to those fields and type arguments instead of repeatedly inspecting dataclasses and annotations.
When compilation happens
API
Default compilation point
Dataclass mixin
Class creation/import time
Mixin with lazy_compilation=True
First serialization/deserialization call
Reusable codec
Encoder/decoder construction
One-off codec function
Every function call creates a disposable codec
This gives two distinct performance dimensions: setup latency and steady-state throughput.
Reuse codecs
The most important optimization for non-dataclass roots is to construct once:
Do not call a one-off function repeatedly for the same shape on a hot path; it regenerates a codec each time.
Eager versus lazy mixins
Eager compilation makes the first call fast and catches unsupported fields early. Lazy compilation reduces import work when an application declares many models but uses few of them:
python
classConfig(BaseConfig):
lazy_compilation =True
Choose based on application startup behavior:
CLI/serverless workloads may benefit from lazy models that are rarely touched.
Long-running services usually benefit from eager failure and predictable first-request latency.
If you choose lazy compilation, warm critical models during startup when latency spikes are unacceptable.
Measure total startup plus first-use cost; import time alone can hide deferred work.
The first lazy call replaces the placeholder method on the class with the generated implementation. A method reference captured before that call keeps pointing to the placeholder and can repeat the compilation path. Warm the model before saving a direct reference, or retain a callable that performs attribute lookup at call time:
The typed conversion stage and the final encoder both contribute to runtime.
Requirement
Candidate
No dependency, maximum compatibility
Standard-library JSON
High JSON throughput, bytes output
orjson
Compact binary internal payload
MessagePack
Human-edited configuration
TOML or YAML; optimize readability first
Framework already parsed JSON
Basic encoder/decoder to avoid re-encoding
orjson can encode datetime, date, time, and UUID natively through its built-in dialect. MessagePack keeps bytes native. These paths can avoid intermediate conversions as well as using a faster final encoder.
Benchmark with your real shapes: deeply nested unions, large primitive lists, custom strategies, and many optional fields stress different parts of the pipeline.
Avoid unnecessary copies carefully
Dialect no_copy_collections can pass selected collection types through:
Use it only when elements need no conversion and the downstream consumer will not mutate input. Eliminating a shallow copy is valuable for large containers but irrelevant for small models, and aliasing mutable objects can be far more expensive than the allocation it saved.
Built-in orjson, TOML, and MessagePack dialects already select safe native-container paths for their encoders.
Prefer fixed behavior when it is fixed
Dynamic code-generation flags add per-call parameters and branches. If a model always omits None, use:
python
classConfig(BaseConfig):
omit_none =True
Enable TO_DICT_ADD_OMIT_NONE_FLAG only when callers genuinely switch behavior. The same applies to aliases and dialect selection.
This is a small optimization, but it also produces a simpler and more stable API.
Tagged unions scale better
An untagged union or discriminator may try multiple variants until one succeeds. A field discriminator maps a tag directly to the intended variant:
The benefit grows with the number and overlap of variants. Tagged payloads also make failure deterministic and easier to debug.
Custom strategy costs
Strategies are compiled into generated calls, but the callable's work still matters.
Reuse immutable strategy instances instead of allocating them per value.
Avoid reparsing constant format strings or calling re.compile() inside serialize().
Use annotation processing only when recursive conversion is needed.
Return the final simple representation directly when possible.
Keep hooks free of network, disk, and global-lock operations.
A Python lambda and a strategy method have similar call overhead in the hot path; choose the clearer reusable design first, then profile.
Omission and payload size
omit_none and omit_default reduce output size and final encoder work, but equality checks and default-factory evaluation have a cost. They are most useful for sparse models and network/storage payloads, not as a blanket micro-optimization.
Do not change omission policy only for speed if consumers distinguish missing from explicit null/default.
Sorting keys
sort_keys=True creates deterministic dataclass field order when Mashumaro generates the serialization method. It does not sort the output dictionary on every call and therefore adds no per-call sorting cost. It also does not sort keys inside arbitrary mapping fields.
Key sorting provided by a format library is a separate operation, not another mashumaro config option. For example, json.dumps(..., sort_keys=True) and orjson.dumps(..., option=orjson.OPT_SORT_KEYS) sort keys at encoding time, including keys in arbitrary nested mappings. Use those library-specific options only when the final JSON needs deterministically ordered object keys.
Measure correctly
A useful benchmark separates:
Import/class construction.
Codec construction.
First lazy call.
Repeated encode/decode.
Typed conversion alone with Basic codecs.
Final format encoding alone.
Allocation/peak memory for large collections.
Use representative payloads and a benchmark runner such as pyperf; follow its guidance for reproducible runs and use enough processes to reduce noise. Compare exact semantics — bytes versus strings, validation strictness, omission, aliases, datetime handling, and unknown keys — before comparing numbers.
Performance checklist
Reuse encoders and decoders.
Avoid round-tripping through JSON when a framework already provides dictionaries.
Pick eager or lazy compilation based on first-use latency.
Use tagged discriminators for large polymorphic sets.
Select orjson/MessagePack when their boundary contract fits.
Benchmark before enabling no_copy_collections.
Keep strategies and hooks deterministic and local.
Test payload semantics before optimizing representation size.
Every encode function takes (obj, shape_type) and every decode function takes (data, shape_type). Standard JSON additionally accepts its transform function as an optional third argument.
Subclass attributes are serialization_strategy, serialize_by_alias, namedtuple_as_dict, omit_none, omit_default, and no_copy_collections. Dialect.merge(other) returns a new dialect class; see Dialects.
Annotated preserves the underlying static type while carrying Mashumaro's runtime metadata. Multiple Alias annotations are tried in order during deserialization; the first is used for alias serialization and generated JSON Schema.
The builder exposes .build(instance_type) and .get_definitions().
Exceptions
Import exceptions from mashumaro.exceptions. Their attributes and recommended handling are documented in Errors and Troubleshooting.
Stable versus internal modules
Build integrations from the public modules shown here. Names under mashumaro.core and generated private methods are implementation details and may change without serving as a user-facing extension contract.
Mashumaro follows Semantic Versioning 2.0.0. Review the GitHub Releases notes before upgrading across a major version and keep exact wire-contract tests for serialized data that outlives a deployment.
Supported Python versions
The current 3.23 release supports Python 3.10–3.15.
Retired Python
Last compatible mashumaro
3.9
3.20
3.8
3.14
3.7
3.9.1
3.6
3.1.1
If an application must stay on an end-of-life interpreter, pin both the library and optional format dependencies. The preferred migration is to upgrade Python and then use the current Mashumaro release.
Enable ADD_DIALECT_SUPPORT for call-time use or set Config.dialect. Ensure the final encoder supports raw binary; standard JSON does not.
use_enum was removed
Use a strategy/dialect with pass_through when the downstream format accepts enum objects, or define an explicit by-name/by-value strategy. Default Mashumaro behavior serializes Enum.value.
use_datetime was removed
Use a dialect that passes datetime, date, and time through. TOML and orjson already ship format dialects with appropriate native handling. For JSON, prefer an explicit textual or numeric strategy because json.dumps does not accept datetime objects by default.
Format method signatures changed
Version 2 accepted dict_params and arbitrary final encoder/decoder keyword arguments. Version 3 forwards extra method keywords to the generated dictionary conversion layer:
The first alias, displayName, is the canonical output name. The remaining aliases are accepted in order during deserialization, and display_name is accepted as the final fallback because allow_deserialization_not_by_alias is enabled.
Change a field representation
A date string changed to epoch seconds is a breaking wire change even if the Python field remains datetime. Introduce a protocol version or a new dialect, read both representations during a defined transition, and emit only the new canonical representation.
Add a polymorphic variant
Tagged discriminators make this additive for new readers, but old readers still reject unknown tags unless they have a supertype fallback. Decide explicitly whether unknown variants should fail, be stored raw, or map to a base model.
Tighten unknown-key handling
Enabling forbid_extra_keys is behaviorally breaking for payloads that previously contained ignored fields. Audit real traffic or stored fixtures first.
Compatibility test suite
Keep fixtures for every supported protocol/storage version and test: