Skip to content

kulapard/python-serialization-comparison

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

27 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

python-serialization-comparison

Comparison of marshmallow, marshmallow-recipe, and pydantic β€” performance benchmarks, feature matrix, and installed size.

All libraries operate on the same plain @dataclasses.dataclass types β€” no library-specific model definitions.

TL;DR

marshmallow marshmallow-recipe mr-recipe nuked pydantic
Speed (vs marshmallow) 1x ~1.3x load / 0.2x dump 6-17x 10-30x
Install size 0.4 MB 1.7 MB (included) 8.1 MB
Backend Pure Python Pure Python Rust (PyO3) Rust (pydantic-core)
Schema definition Hand-written Auto from dataclasses (same) Auto (multiple ways)
Features Flexible hooks, plugins Minimal, ergonomic (same) Richest (JSON Schema, OpenAPI, strict mode, generics)
Best for Existing marshmallow codebases Dataclass-centric projects on marshmallow (same, need speed) New projects needing speed + features

Model structure

A 10-level-deep Organization hierarchy with lists, nested objects, enums, Decimal, date, datetime, and Optional fields:

L1   Organization
L2   └── Department
L3       └── Team
L4           └── Employee
L5               β”œβ”€β”€ Contract
L6               β”‚   └── Terms
L7               β”‚       └── Clause
L5               └── Project
L6                   └── Task
L7                       └── SubTask
L8                           └── Comment
L9                               └── Reaction
L10                                  └── Author

Every dataclass has 10-13 fields (str, int, bool, Decimal, date, datetime, Enum, Optional, list). A single Organization object contains 2 departments x 2 teams x 2 employees, each with 1 contract (2 clauses) and 2 projects (2 tasks each, 2 subtasks each, 1 comment with 2 reactions).

Libraries under test

Library Version Backend How it's used
marshmallow 3.26.2 Pure Python Hand-written Schema classes with @post_load
marshmallow-recipe 0.0.93 Pure Python mr.dump(obj) / mr.load(Cls, data) β€” auto-generated from dataclasses
marshmallow-recipe (nuked) 0.0.93 Rust (PyO3) mr.nuked.dump(Cls, obj) / mr.nuked.load(Cls, data)
pydantic 2.12.5 Rust (pydantic-core) TypeAdapter(Cls).dump_python(obj) / .validate_python(data)

Test conditions

Parameter Value
Date 2026-04-11
Machine Apple M1 Pro (8 cores), 32 GB RAM
OS macOS 26.3.1 (arm64)
Python 3.14.0
Timing Median of adaptive runs (min 10 / max 500 rounds, 10 s budget)
GC gc.collect() before each test, disabled during measurement
Scales 1, 100, 1000 objects per call
Total run time ~15 min

Results

Times

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Operation   β”‚ marshmallow β”‚ mr-recipe β”‚ mr-recipe-nuked β”‚ pydantic β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 1    β”‚ 3.3 ms      β”‚ 14.6 ms   β”‚ 538.9 us        β”‚ 299.0 us β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 100  β”‚ 333.0 ms    β”‚ 1.48 s    β”‚ 59.7 ms         β”‚ 32.8 ms  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 1000 β”‚ 3.34 s      β”‚ 15.13 s   β”‚ 610.0 ms        β”‚ 342.3 ms β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 1    β”‚ 13.1 ms     β”‚ 10.1 ms   β”‚ 786.5 us        β”‚ 434.4 us β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 100  β”‚ 1.28 s      β”‚ 1.01 s    β”‚ 82.7 ms         β”‚ 48.9 ms  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 1000 β”‚ 12.90 s     β”‚ 9.95 s    β”‚ 824.0 ms        β”‚ 497.3 ms β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Speedup vs marshmallow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Operation   β”‚ vs mr-recipe β”‚ vs mr-recipe-nuked β”‚ vs pydantic β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 1    β”‚ 1/4.5x       β”‚ 6.1x               β”‚ 10.9x       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 100  β”‚ 1/4.5x       β”‚ 5.6x               β”‚ 10.2x       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 1000 β”‚ 1/4.5x       β”‚ 5.5x               β”‚ 9.8x        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 1    β”‚ 1.3x         β”‚ 16.6x              β”‚ 30.1x       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 100  β”‚ 1.3x         β”‚ 15.5x              β”‚ 26.2x       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 1000 β”‚ 1.3x         β”‚ 15.7x              β”‚ 25.9x       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

marshmallow-recipe: standard vs nuked

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Operation   β”‚ mr-recipe β”‚ mr-recipe-nuked β”‚ Speedup β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 1    β”‚ 14.6 ms   β”‚ 538.9 us        β”‚ 27.2x   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 100  β”‚ 1.48 s    β”‚ 59.7 ms         β”‚ 24.9x   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 1000 β”‚ 15.13 s   β”‚ 610.0 ms        β”‚ 24.8x   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 1    β”‚ 10.1 ms   β”‚ 786.5 us        β”‚ 12.8x   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 100  β”‚ 1.01 s    β”‚ 82.7 ms         β”‚ 12.2x   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 1000 β”‚ 9.95 s    β”‚ 824.0 ms        β”‚ 12.1x   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Installed size

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Library (with dependencies)           β”‚ Total  β”‚ Breakdown                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ marshmallow                           β”‚ 0.4 MB β”‚ marshmallow 0.4 MB                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ marshmallow-recipe (+ marshmallow)    β”‚ 1.7 MB β”‚ mr-recipe 1.3 MB + marshmallow 0.4 MBβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ pydantic (+ pydantic-core)            β”‚ 8.1 MB β”‚ pydantic 3.8 MB + core 4.4 MB        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Rust binary (.so) sizes:

  • marshmallow-recipe _nuked.so β€” 0.7 MB
  • pydantic-core _pydantic_core.so β€” 4.0 MB

Peak memory (single call)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Operation   β”‚ marshmallow β”‚ mr-recipe β”‚ mr-recipe-nuked β”‚ pydantic β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 1    β”‚ 484.5 KB    β”‚ 1.4 MB    β”‚ 241.0 KB        β”‚ 178.0 KB β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 100  β”‚ 22.6 MB     β”‚ 21.9 MB   β”‚ 21.4 MB         β”‚ 17.3 MB  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dump x 1000 β”‚ 226.1 MB    β”‚ 217.0 MB  β”‚ 214.3 MB        β”‚ 173.1 MB β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 1    β”‚ 195.2 KB    β”‚ 193.0 KB  β”‚ 185.0 KB        β”‚ 319.4 KB β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 100  β”‚ 15.5 MB     β”‚ 15.8 MB   β”‚ 15.5 MB         β”‚ 31.1 MB  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ load x 1000 β”‚ 154.4 MB    β”‚ 157.6 MB  β”‚ 155.1 MB        β”‚ 311.4 MB β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Feature comparison

Schema definition

Feature marshmallow marshmallow-recipe pydantic
Hand-written schemas Yes β€” Yes (BaseModel)
Auto from dataclasses β€” Yes Yes (TypeAdapter)
Auto from TypedDict β€” β€” Yes
Dynamic model creation β€” β€” Yes (create_model())
Generic models β€” β€” Yes (Generic[T])
Computed fields β€” β€” Yes (@computed_field)

Validation

Feature marshmallow marshmallow-recipe pydantic
Built-in validators Range, Length, OneOf, NoneOf, Equal, Regexp, URL, Email regexp, email All via Annotated constraints (gt, lt, min_length, pattern, ...)
Custom validators @validates, @validates_schema mr.validate(fn) @field_validator, @model_validator
Before/after modes β€” β€” Yes (mode='before'/'after'/'wrap')
Strict mode β€” β€” Yes (global or per-field)
Validate on assignment β€” β€” Yes (validate_assignment=True)
Validate defaults β€” β€” Yes (validate_default=True)
Fail fast (stop on first error) β€” β€” Yes (FailFast)

Serialization & deserialization

Feature marshmallow marshmallow-recipe pydantic
Dump to dict schema.dump(obj) mr.dump(obj) adapter.dump_python(obj)
Load from dict schema.load(data) mr.load(Cls, data) adapter.validate_python(data)
Dump to JSON string β€” β€” Yes (dump_json())
Load from JSON string β€” β€” Yes (validate_json())
Load from string values β€” β€” Yes (validate_strings())
Many (batch) many=True dump_many() / load_many() Yes (via TypeAdapter(list[T]))
Partial loading Yes (partial=True) β€” β€”
Nested objects Yes (Nested()) Yes (auto) Yes (auto)

Pre/post processing hooks

Feature marshmallow marshmallow-recipe pydantic
pre_load Yes Yes (@mr.pre_load) Yes (mode='before')
post_load Yes β€” Yes (model_post_init)
pre_dump Yes β€” β€”
post_dump Yes β€” β€”

Field types

Type marshmallow marshmallow-recipe pydantic
str, int, float, bool Yes Yes Yes
Decimal Yes Yes Yes
datetime, date, time Yes Yes Yes
timedelta Yes β€” Yes
UUID Yes Yes Yes
Enum (str/int) Yes Yes Yes
list, set, frozenset Yes Yes Yes
dict Yes Yes Yes
tuple (homogeneous) Yes Yes Yes
tuple (heterogeneous) Yes Standard only Yes
Union β€” Yes Yes
Discriminated union β€” β€” Yes
Literal β€” Yes Yes
Optional Yes Yes Yes
bytes Yes Yes Yes
IP address / network Yes β€” Yes
URL Yes β€” Yes
Email (validated) Yes Yes (validator) Yes (EmailStr)
FilePath / DirectoryPath β€” β€” Yes
SecretStr / SecretBytes β€” β€” Yes
Json (raw) β€” Yes (JsonRawField) Yes (Json)
Base64 β€” β€” Yes
PaymentCardNumber β€” β€” Yes

Naming & aliasing

Feature marshmallow marshmallow-recipe pydantic
Per-field alias Yes (data_key) Yes (mr.meta(name=...)) Yes (Field(alias=...))
Separate validation/serialization alias β€” β€” Yes
camelCase convention Manual Yes (CAMEL_CASE) Yes (alias_generator)
UPPER_SNAKE_CASE Manual Yes (UPPER_SNAKE_CASE) Yes (alias_generator)
CapitalCamelCase Manual Yes (CAPITAL_CAMEL_CASE) Yes (alias_generator)

Ecosystem & integrations

Feature marshmallow marshmallow-recipe pydantic
JSON Schema generation Via plugin Yes (mr.json_schema()) Yes (built-in)
OpenAPI support Via apispec β€” Yes (built-in)
Settings management β€” β€” Via pydantic-settings
None value handling β€” Yes (NoneValueHandling) Yes (via serialization options)
Unknown fields handling RAISE / EXCLUDE / INCLUDE β€” 'forbid' / 'ignore' / 'allow'

Key takeaways

Performance

  • pydantic v2 is the fastest overall (10-30x over marshmallow), powered by its Rust core
  • marshmallow-recipe nuked is a strong second (6-17x over marshmallow), powered by Rust via PyO3
  • marshmallow-recipe standard (mr.dump/mr.load) is ~4.5x slower than raw marshmallow for dump, ~1.3x faster for load
  • The nuked backend of marshmallow-recipe is 12-27x faster than its standard backend
  • Performance gaps widen with deeper nesting compared to flat models

Peak memory

  • For dump, all libraries use similar memory at scale; pydantic is the most memory-efficient (~173 MB for 1000 objects vs ~226 MB for marshmallow)
  • For load, pydantic uses 2x more memory than the others (311 MB vs ~155 MB for 1000 objects) β€” the Rust core creates richer internal representations
  • At small scale (x 1), all libraries are lightweight (<500 KB), except mr-recipe standard dump (1.4 MB)

Installed size

  • marshmallow is the lightest at 0.4 MB β€” pure Python, zero compiled code
  • marshmallow-recipe is 1.7 MB total (with marshmallow); its Rust binary is only 0.7 MB β€” 5.7x smaller than pydantic-core's
  • pydantic is the heaviest at 8.1 MB (with pydantic-core) β€” nearly 5x larger than marshmallow-recipe, though the Rust core is what makes it the fastest

Features

  • pydantic has the richest feature set: JSON Schema, OpenAPI, strict mode, discriminated unions, computed fields, generics, direct JSON string serialization, and the widest field type coverage
  • marshmallow offers the most flexible hook system (pre/post load/dump), partial loading, and a mature plugin ecosystem (apispec, marshmallow-jsonschema)
  • marshmallow-recipe is the most ergonomic for dataclass-heavy codebases: zero boilerplate schema definitions, built-in naming conventions (camelCase, UPPER_SNAKE), and JSON Schema generation β€” all while staying compatible with the marshmallow ecosystem

When to use what

  • pydantic β€” best overall choice when performance, features, and JSON/OpenAPI integration matter most, and install size is not a concern
  • marshmallow-recipe (nuked) β€” best for dataclass-centric projects that need fast serialization with a small footprint, especially when already using marshmallow
  • marshmallow β€” best when you need maximum control over schema behavior, custom hooks, or have an existing marshmallow-based codebase

Running

make bench

This uses uv to auto-install dependencies and run the benchmark.

Or manually:

uv run bench.py

About

Benchmark comparing marshmallow, marshmallow-recipe, and pydantic serialization performance

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors