mrkeyoor.com_
Fri 07 Aug 20:56 UTC
PyPIUtilsupdated 07 Aug 2026

mashumaro

mashumaro serialises and deserialises ordinary Python dataclasses, and it does it by generating source code. When your class is created, mashumaro reads the type annotations and writes a from_dict and a to_dict function tailored to exactly those fields, then compiles them and attaches them to the class. Nothing is inspected at call time, so decoding a nested structure costs roughly what a hand-written loop would cost. You opt in either by inheriting a mixin such as DataClassDictMixin or DataClassJSONMixin, or by building a standalone codec object for any shape you like, including List[Dict[str, MyClass]] or a bare TypedDict that owns no mixin. Built-in codecs cover plain dicts, the standard json module, orjson, YAML, TOML and MessagePack, all sharing the same type handling. It is a converter, not a validator: it turns wire data into the annotated types and complains when it cannot, but it does not enforce field constraints or collect a list of errors for you.

Verdict

If your domain objects are already dataclasses and serialisation shows up in your profiles, mashumaro is the fastest way to keep them plain and still get fast codecs for five wire formats. Do not reach for it as a pydantic replacement at an API boundary, because it converts types without validating them.

API stability4/5The mixins, the field_options helper and the BaseConfig class have held their shape across the 3.x line, and new behaviour keeps arriving as opt-in config flags rather than changed defaults. Point releases are frequent (3.18 through 3.22 all landed between January and May 2026), so a version range rather than a hard pin is the sane choice.
Docs4/5The README is the documentation and it is exhaustive, with a runnable example for essentially every option including discriminators, dialects and JSON Schema generation. What it lacks is structure: there is no searchable API reference, so finding the right option means scrolling one enormous page or trusting the table of contents anchors.
Maintenance4/53.22 shipped on 26 May 2026 and the repository was pushed on 23 July 2026, with only 15 open issues plus 5 open PRs, which is a tidy tracker for a project this old. The reservation is bus factor: it is essentially one maintainer, and has been since 2018.
Ecosystem3/5Around 9.2M weekly downloads, but a large share arrives transitively rather than through direct adoption, most visibly via dbt-core which depends on mashumaro[msgpack]. There is no plugin ecosystem to speak of; extension happens through SerializationStrategy and dialects inside your own code.

Use it if

  • Your models are already plain dataclasses, NamedTuples or TypedDicts and you want serialisation without rewriting them as a model framework's base class
  • Decoding sits in a hot path where per-record overhead shows up in profiles, for example a worker chewing through millions of JSON lines or MessagePack frames
  • You need one type-handling story across several wire formats: the same dataclass round-trips through json, orjson, YAML, TOML and MessagePack with the same field options
  • You have to serialise third-party types you cannot modify, which is what SerializationStrategy exists for, or tagged unions of subclasses, which is what Discriminator exists for
  • You want to decode shapes that are not dataclasses at all, since BasicDecoder and JSONDecoder accept any typing construct rather than requiring a model class
Skip it if

Setup reality

pip install mashumaro is genuinely light: the only hard dependency is typing_extensions 4.14 or newer, and Python 3.10 or newer is required. Wire formats come as extras, so you want mashumaro[orjson], mashumaro[msgpack], mashumaro[yaml] or mashumaro[toml] depending on what you actually encode, and asking for DataClassORJSONMixin without the extra fails at import. The first real trip hazard is that the mixin does not make your class a dataclass. You still write @dataclass yourself above it, and forgetting produces a class whose generated methods see no fields at all. The second is forward references: because code is generated when the class is created, a type name that is not resolvable at that moment raises UnresolvedTypeReferenceError, which bites as soon as you add from __future__ import annotations or define a model inside a function. The allow_postponed_evaluation config option defers resolution to first call and is the usual fix. The third is startup cost: with mixins, every model compiles its methods at import time, so a package with hundreds of dataclasses adds measurable seconds to process boot until you enable lazy_compilation or move to codecs, which compile when the codec is constructed.

Patterns

Round-trip a dataclass through a plain dictdataclass-to-dict

from dataclasses import dataclass
from datetime import datetime
from mashumaro import DataClassDictMixin

@dataclass
class Event(DataClassDictMixin):
    id: int
    name: str
    at: datetime

Event.from_dict({"id": 1, "name": "push", "at": "2026-08-07T10:00:00"})
Event(1, "push", datetime.now()).to_dict()

The @dataclass decorator is still yours to write; the mixin only adds the generated methods. Leave it off and you get a class with no fields, from_dict returning an empty instance, and no error to tell you why.

Encode and decode JSON strings directlyjson-mixin

from dataclasses import dataclass
from mashumaro.mixins.json import DataClassJSONMixin

@dataclass
class User(DataClassJSONMixin):
    id: int
    email: str

User.from_json('{"id": 1, "email": "a@b.c"}')
User(1, "a@b.c").to_json()

DataClassJSONMixin already inherits DataClassDictMixin, so you get from_dict and to_dict for free and should not list both mixins. It uses the standard library json module; swap to the orjson mixin if encoding shows up in your profile.

Build a codec for a shape that is not a dataclassreusable-codec

from typing import Dict, List
from mashumaro.codecs.json import JSONDecoder, JSONEncoder

decoder = JSONDecoder(Dict[str, List[Event]])
encoder = JSONEncoder(Dict[str, List[Event]])

data = decoder.decode(payload)
wire = encoder.encode(data)

Build the decoder once at module scope and reuse it. The convenience functions such as json_decode(payload, SomeType) construct a throwaway decoder on every call, which means recompiling the generated code each time and losing the entire performance argument for using this library.

Map wire names onto Python namesfield-aliases

from dataclasses import dataclass, field
from mashumaro import DataClassDictMixin, field_options
from mashumaro.config import BaseConfig

@dataclass
class Item(DataClassDictMixin):
    item_id: int = field(metadata=field_options(alias="itemId"))
    unit_price: float = 0.0

    class Config(BaseConfig):
        aliases = {"unit_price": "unitPrice"}
        serialize_by_alias = True
        allow_deserialization_not_by_alias = True

Aliases apply to input only until you set serialize_by_alias, so the default behaviour is asymmetric: from_dict wants itemId but to_dict emits item_id. allow_deserialization_not_by_alias is what lets both spellings decode, which matters when you are migrating a payload format.

Drop None and default values from outputomit-empty-fields

from dataclasses import dataclass
from typing import Optional
from mashumaro import DataClassDictMixin
from mashumaro.config import BaseConfig

@dataclass
class Patch(DataClassDictMixin):
    name: Optional[str] = None
    retries: int = 3

    class Config(BaseConfig):
        omit_none = True
        omit_default = True

Patch().to_dict()  # {}

omit_default compares against the declared default or the default_factory result on every serialisation, so it is not free on wide models. These are class-level switches; if you need it decided per call, add TO_DICT_ADD_OMIT_NONE_FLAG to code_generation_options and pass to_dict(omit_none=True).

Teach it a type you do not owncustom-serialization-strategy

from dataclasses import dataclass
from decimal import Decimal
from mashumaro import DataClassDictMixin
from mashumaro.config import BaseConfig
from mashumaro.types import SerializationStrategy

class DecimalAsString(SerializationStrategy):
    def serialize(self, value: Decimal) -> str:
        return format(value, "f")

    def deserialize(self, value: str) -> Decimal:
        return Decimal(value)

@dataclass
class Order(DataClassDictMixin):
    total: Decimal

    class Config(BaseConfig):
        serialization_strategy = {Decimal: DecimalAsString()}

Registering the strategy in Config applies it to every Decimal field on the class and anything it inherits, which is what you want for money. For a one-off field use field_options(serialization_strategy=...) instead, and remember the field option always beats the config entry.

Decode a subclass hierarchy by a tag fielddiscriminated-union

from dataclasses import dataclass
from typing import Annotated, List
from mashumaro import DataClassDictMixin
from mashumaro.types import Discriminator

@dataclass
class Message(DataClassDictMixin):
    pass

@dataclass
class Text(Message):
    type = "text"
    body: str

@dataclass
class Image(Message):
    type = "image"
    url: str

@dataclass
class Batch(DataClassDictMixin):
    items: List[Annotated[Message, Discriminator(field="type", include_subtypes=True)]]

The tag attribute has to be defined on the subclass itself, because mashumaro reads it from that class's __dict__; a subclass that inherits the tag rather than declaring it is skipped. Only Final, Literal and StrEnum tag annotations are written back out during serialisation, so a bare class attribute decodes but does not re-encode.

Use orjson for the fast pathorjson-codec

# pip install mashumaro[orjson]
from dataclasses import dataclass
from datetime import datetime
from mashumaro.mixins.orjson import DataClassORJSONMixin

@dataclass
class Tick(DataClassORJSONMixin):
    symbol: str
    at: datetime

Tick.from_json(raw)
Tick("AAPL", datetime.now()).to_jsonb()   # bytes, no decode step

orjson handles datetime, date, time and UUID natively here, so those fields skip mashumaro's own conversion and the output format is orjson's rather than isoformat() through the standard mixin. to_jsonb returns bytes and is the one to use when the next hop is a socket or a Kafka producer.

Encode to MessagePackmsgpack-codec

# pip install mashumaro[msgpack]
from dataclasses import dataclass
from mashumaro.mixins.msgpack import DataClassMessagePackMixin

@dataclass
class Frame(DataClassMessagePackMixin):
    seq: int
    payload: bytes

blob = Frame(1, b"\x00\x01").to_msgpack()
Frame.from_msgpack(blob)

MessagePack keeps bytes fields as bytes instead of base64-encoding them the way the JSON path has to, which is the main reason to pick it. This is also the exact extra dbt-core depends on, so if you are inside a dbt plugin the package is already installed.

Reject payloads with unknown fieldsforbid-extra-keys

from dataclasses import dataclass
from mashumaro import DataClassDictMixin
from mashumaro.config import BaseConfig

@dataclass
class Config_(DataClassDictMixin):
    host: str

    class Config(BaseConfig):
        forbid_extra_keys = True

Config_.from_dict({"host": "a", "prot": 80})  # ExtraKeysError

Off by default, so a typo in a config file or a renamed API field is silently dropped rather than reported. Turning it on is the closest thing this library has to validation, and it is worth doing on anything humans hand-edit.

Normalise data before and after conversionserialization-hooks

from dataclasses import dataclass
from typing import Any, Dict
from mashumaro import DataClassDictMixin

@dataclass
class Legacy(DataClassDictMixin):
    user_id: int

    @classmethod
    def __pre_deserialize__(cls, d: Dict[Any, Any]) -> Dict[Any, Any]:
        return {k.lower(): v for k, v in d.items()}

    def __post_serialize__(self, d: Dict[Any, Any]) -> Dict[Any, Any]:
        d.pop("internal", None)
        return d

The four hooks are __pre_deserialize__ and __post_deserialize__ as classmethods, __pre_serialize__ and __post_serialize__ as instance methods. They run on every call, so a dict comprehension over the whole payload in __pre_deserialize__ undoes a good chunk of the speed you came for.

See what was generated, and defer generating itinspect-generated-code

from dataclasses import dataclass
from mashumaro import DataClassDictMixin
from mashumaro.config import BaseConfig

@dataclass
class Model(DataClassDictMixin):
    x: int

    class Config(BaseConfig):
        debug = True             # prints the generated from_dict/to_dict
        lazy_compilation = True  # compile on first call, not at import

debug is the only practical way to understand a confusing traceback, since the frame it points at is synthesised source with no file on disk. With lazy_compilation on, do not stash a reference to Model.from_dict at import time: the real method does not exist yet, so capture it in a lambda instead.

Alternatives

PackageRegistryPick it when
pydanticPyPIYou need real validation with field constraints and error reports, or you want the framework integrations that come with it
msgspecPyPIYou want the speed plus schema validation in a C extension and can accept a smaller set of supported Python types
cattrsPyPIYou prefer converters configured outside the class over generated methods attached to it, especially with attrs classes