Migration Guide
This guide helps you migrate from other popular Python serialization libraries to lodum, and documents changes between lodum versions.
Migrating from previous Lodum versions
Exception Standardization (v0.2.0)
In versions prior to v0.2.0, the YAML and Pickle loaders raised TypeError when a field's type did not match the expected type during deserialization. This has been standardized to lodum.exception.DeserializationError to match the behavior of other formats like JSON and MsgPack.
Before:
After:
from lodum.exception import DeserializationError
try:
lodum.yaml.loads(MyClass, bad_data)
except DeserializationError:
# handle error
Centralized Schema API (v0.2.0)
Starting with v0.2.0, the primary way to generate a JSON Schema from a @lodum class is through the top-level lodum.schema() function. Format-specific schema functions (like lodum.msgpack.schema()) have been removed from binary formats to reduce API surface area and architectural redundancy.
Before:
After:
Note:json.schema() and yaml.schema() remain available as secondary entry points for convenience.
Key Differences
lodum is inspired by Rust's serde framework. Its primary differences from other Python libraries are:
- Format Agnostic:
lodumseparates the definition of your data structure (using@lodum) from the data format (JSON, YAML, TOML, MsgPack, etc.). - Bytecode Compilation:
lodumgenerates specialized Python bytecode for your classes at runtime. This provides performance comparable to hand-written code while remaining pure Python. __init__-Centric:lodumuses your class's__init__method and its type hints as the source of truth for the data structure. This ensures that your objects are always instantiated through their standard constructor.
Migrating from Pydantic
Pydantic is a popular library that uses BaseModel and class attributes to define data structures.
Class Definition
Pydantic:
from pydantic import BaseModel
class User(BaseModel):
id: int
username: str
email: str | None = None
lodum:
from lodum import lodum
from typing import Optional
@lodum
class User:
def __init__(self, id: int, username: str, email: Optional[str] = None):
self.id = id
self.username = username
self.email = email
@dataclass with @lodum.
Serialization
Pydantic:
lodum:
from lodum import json
# lodum doesn't have a generic 'model_dump', but you can dump to any format
user_json = json.dumps(user)
# If you just want a dict:
from lodum.internal import dump
from lodum.core import BaseDumper
user_dict = dump(user, BaseDumper())
Deserialization
Pydantic:
lodum:
from lodum import json
user = json.loads(User, data_json)
# From a dict:
from lodum.internal import load
from lodum.json import JSONLoader # or any other loader
user = load(User, JSONLoader(data_dict))
Field Customization
Pydantic:
from pydantic import BaseModel, Field
class User(BaseModel):
user_id: int = Field(alias="id")
password: str = Field(exclude=True)
lodum:
from lodum import lodum, field
@lodum
class User:
def __init__(
self,
user_id: int = field(rename="id"),
password: str = field(skip_serializing=True)
):
self.user_id = user_id
self.password = password
Migrating from Marshmallow
Marshmallow uses separate Schema classes to define how data is serialized and deserialized.
Definition and Usage
Marshmallow:
from marshmallow import Schema, fields, post_load
class User:
def __init__(self, id, name):
self.id = id
self.name = name
class UserSchema(Schema):
id = fields.Int(data_key="user_id")
name = fields.Str()
@post_load
def make_user(self, data, **kwargs):
return User(**data)
schema = UserSchema()
result = schema.load({"user_id": 1, "name": "Alice"})
lodum:
from lodum import lodum, field, json
@lodum
class User:
def __init__(self, id: int = field(rename="user_id"), name: str = ""):
self.id = id
self.name = name
user = json.loads(User, '{"user_id": 1, "name": "Alice"}')
lodum eliminates the need for a separate Schema class and the @post_load boilerplate.
Migrating from Dataclasses (with mashumaro/dacite)
If you are already using dataclasses with a library like mashumaro, the transition to lodum is very smooth.
mashumaro:
from dataclasses import dataclass
from mashumaro import DataClassJSONMixin
@dataclass
class Point(DataClassJSONMixin):
x: int
y: int
lodum:
from dataclasses import dataclass
from lodum import lodum, json
@lodum
@dataclass
class Point:
x: int
y: int
# Usage
json_str = json.dumps(Point(1, 2))
p = json.loads(Point, json_str)
lodum offers a similar performance profile to mashumaro through bytecode generation, but provides a more unified interface for multiple binary and text formats out of the box.
Migrating from Attrs and Cattrs
attrs is an alternative to dataclasses, and cattrs handles the conversion to/from structured data.
Attrs/Cattrs:
import attr
import cattr
@attr.s
class User:
id = attr.ib(type=int)
name = attr.ib(type=str)
user = cattr.structure({"id": 1, "name": "Alice"}, User)
lodum:
from lodum import lodum, json
@lodum
class User:
def __init__(self, id: int, name: str):
self.id = id
self.name = name
user = json.loads(User, '{"id": 1, "name": "Alice"}')
cattrs is very flexible, lodum provides a more integrated experience with direct support for various wire formats.