Lodum provides a unified API for serializing to and deserializing from multiple data formats. Define your data structure once with @lodum, then encode it to any supported format.
Architecture
Lodum’s architecture separates concerns like Rust’s serde:
- Data Structures: Classes decorated with
@lodum define what data to encode
- Format Handlers:
Dumper and Loader implementations handle how to encode
This separation makes the library format-agnostic - you can switch formats by changing the import.
from lodum import lodum, json, yaml, msgpack
@lodum
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
user = User(name="Alex", age=30)
# Same object, different formats
json_str = json.dumps(user)
yaml_str = yaml.dumps(user)
msgpack_bytes = msgpack.dumps(user)
JSON
Module: lodum.json
from lodum import lodum, json
@lodum
class Config:
def __init__(self, host: str, port: int):
self.host = host
self.port = port
config = Config(host="localhost", port=8080)
# Serialize
json_str = json.dumps(config)
# {"host": "localhost", "port": 8080}
# Deserialize
config = json.loads(Config, '{"host": "0.0.0.0", "port": 3000}')
API:
json.dump(obj, target=None, **kwargs) - Encode to string or file
json.dumps(obj, **kwargs) - Encode to string (legacy alias)
json.load(cls, source, max_size=10MB) - Decode from string/file
json.loads(cls, json_string) - Decode from string (legacy alias)
json.stream(cls, source) - Lazy iteration over JSON arrays
json.schema(cls) - Generate JSON Schema
Binary Data: Encoded as Base64 strings
YAML
Module: lodum.yaml
from lodum import lodum, yaml
@lodum
class User:
def __init__(self, name: str, roles: list[str]):
self.name = name
self.roles = roles
user = User(name="Alice", roles=["admin", "editor"])
yaml_str = yaml.dumps(user)
# name: Alice
# roles:
# - admin
# - editor
user = yaml.loads(User, yaml_str)
API:
yaml.dumps(obj) - Encode to YAML string
yaml.loads(cls, yaml_string) - Decode from YAML string
Binary Data: Native binary representation
TOML
Module: lodum.toml
from lodum import lodum, toml
@lodum
class Database:
def __init__(self, host: str, port: int, timeout: float):
self.host = host
self.port = port
self.timeout = timeout
db = Database(host="127.0.0.1", port=5432, timeout=30.0)
toml_str = toml.dumps(db)
# host = "127.0.0.1"
# port = 5432
# timeout = 30.0
API:
toml.dumps(obj) - Encode to TOML string
toml.loads(cls, toml_string) - Decode from TOML string
Binary Data: Encoded as Base64 strings
MessagePack
Module: lodum.msgpack
from lodum import lodum, msgpack
@lodum
class Packet:
def __init__(self, data: bytes, timestamp: int):
self.data = data
self.timestamp = timestamp
packet = Packet(data=b"\x00\x01\x02", timestamp=1234567890)
# Returns bytes
msgpack_bytes = msgpack.dumps(packet)
packet = msgpack.loads(Packet, msgpack_bytes)
API:
msgpack.dumps(obj) - Encode to MessagePack bytes
msgpack.loads(cls, msgpack_bytes) - Decode from MessagePack bytes
Binary Data: Native binary representation (efficient)
CBOR
Module: lodum.cbor
from lodum import lodum, cbor
@lodum
class Sensor:
def __init__(self, reading: float, data: bytes):
self.reading = reading
self.data = data
sensor = Sensor(reading=23.5, data=b"raw")
cbor_bytes = cbor.dumps(sensor)
sensor = cbor.loads(Sensor, cbor_bytes)
API:
cbor.dumps(obj) - Encode to CBOR bytes
cbor.loads(cls, cbor_bytes) - Decode from CBOR bytes
Binary Data: Native binary representation
BSON
Module: lodum.bson
from lodum import lodum, bson
@lodum
class Document:
def __init__(self, title: str, content: str):
self.title = title
self.content = content
doc = Document(title="Hello", content="World")
bson_bytes = bson.dumps(doc)
doc = bson.loads(Document, bson_bytes)
API:
bson.dumps(obj) - Encode to BSON bytes
bson.loads(cls, bson_bytes) - Decode from BSON bytes
Binary Data: Native binary representation
Pickle (Secure)
Module: lodum.pickle
Pickle is inherently insecure. Only deserialize data from trusted sources. Lodum provides a SafeUnpickler that restricts deserialization to safe types.
from lodum import lodum, pickle
@lodum
class State:
def __init__(self, data: dict):
self.data = data
state = State(data={"key": "value"})
pickle_bytes = pickle.dumps(state)
state = pickle.loads(State, pickle_bytes)
Security Features:
- Only allows
lodum-decorated classes
- Restricts to safe built-in types (
int, str, list, etc.)
- Blocks dangerous modules (
os, sys, subprocess)
API:
pickle.dumps(obj) - Encode with validation
pickle.loads(cls, pickle_bytes) - Decode with SafeUnpickler
Dumper Protocol
All format handlers implement the Dumper protocol from src/lodum/core.py:133-191:
class Dumper(Protocol):
"""Defines the interface for a data format dumper (encoder)."""
def dump_int(self, value: int, depth: int = 0, seen: Optional[set] = None) -> Any: ...
def dump_str(self, value: str, depth: int = 0, seen: Optional[set] = None) -> Any: ...
def dump_float(self, value: float, depth: int = 0, seen: Optional[set] = None) -> Any: ...
def dump_bool(self, value: bool, depth: int = 0, seen: Optional[set] = None) -> Any: ...
def dump_bytes(self, value: bytes, depth: int = 0, seen: Optional[set] = None) -> Any: ...
def dump_none(self, depth: int = 0, seen: Optional[set] = None) -> Any: ...
def dump_list(self, value: List[Any], depth: int = 0, seen: Optional[set] = None) -> Any: ...
def dump_dict(self, value: Dict[str, Any], depth: int = 0, seen: Optional[set] = None) -> Any: ...
def begin_struct(self, cls: Type) -> Any: ...
def end_struct(self) -> Any: ...
def field(self, name: str, value: Any, handler: Callable, depth: int = 0, seen: Optional[set] = None) -> None: ...
def begin_list(self) -> None: ...
def end_list(self) -> Any: ...
def list_item(self, value: Any, handler: Callable, depth: int = 0, seen: Optional[set] = None) -> None: ...
Loader Protocol
All format handlers implement the Loader protocol from src/lodum/core.py:321-338:
class Loader(Protocol):
"""Defines the interface for a data format loader (decoder)."""
def load_int(self) -> int: ...
def load_str(self) -> str: ...
def load_float(self) -> float: ...
def load_bool(self) -> bool: ...
def load_bytes(self) -> bytes: ...
def load_list(self) -> Iterator["Loader"]: ...
def load_dict(self) -> Iterator[tuple[str, "Loader"]]: ...
def load_any(self) -> Any: ...
def mark(self) -> Any: ...
def rewind(self, marker: Any) -> None: ...
def get_dict(self) -> Optional[Union[Dict[str, Any], List[Any]]]: ...
def load_bytes_value(self, value: Any) -> bytes: ...
Streaming Serialization
For large datasets, use streaming to achieve O(1) memory usage:
import sys
from lodum import lodum, json
@lodum
class LargeData:
def __init__(self, items: list[int]):
self.items = items
data = LargeData(items=list(range(1_000_000)))
# Write directly to stdout without building full string in memory
json.dump(data, sys.stdout)
# Or to a file
with open("data.json", "w") as f:
json.dump(data, f)
Streaming Loaders:
from lodum import lodum, json
@lodum
class Item:
def __init__(self, id: int, name: str):
self.id = id
self.name = name
# Lazy iteration over JSON array - requires 'ijson' package
with open("items.json", "rb") as f:
for item in json.stream(Item, f):
print(item.name)
Streaming deserialization requires the ijson package: pip install lodum[ijson]
Round-Trip Conversions
Data remains consistent across format conversions:
import json as std_json
from lodum import lodum, json, yaml
@lodum
class ServerConfig:
def __init__(self, host: str, port: int, services: list[str]):
self.host = host
self.port = port
self.services = services
# Start with JSON
original_json = '{"host": "127.0.0.1", "port": 8080, "services": ["users", "products"]}'
# Decode from JSON
config = json.loads(ServerConfig, original_json)
# Encode to YAML
yaml_output = yaml.dumps(config)
# Decode from YAML
config = yaml.loads(ServerConfig, yaml_output)
# Encode back to JSON
final_json = json.dumps(config)
# Verify consistency
assert std_json.loads(original_json) == std_json.loads(final_json)
Installation
Install format support individually:
pip install lodum # JSON only (built-in)
pip install lodum[yaml] # + YAML
pip install lodum[toml] # + TOML
pip install lodum[msgpack] # + MessagePack
pip install lodum[cbor] # + CBOR
pip install lodum[bson] # + BSON
pip install lodum[all] # All formats