Field Customization
Customize individual fields using the field() function as a default value in your __init__ method.
Complete Example
from lodum import lodum, field, json
@lodum
class User :
def __init__ (
self ,
# Rename 'user_id' to 'id' in the output
user_id : int = field( rename = "id" , default = 0 ),
# This field is required
email : str ,
# This field will not be included in the output
password_hash : str = field( skip_serializing = True , default = "" ),
# If 'prefs' is missing on decoding, it defaults to an empty dict
prefs : dict = field( default_factory = dict ),
# Add validation to a field
age : int = field( validate = lambda x : x >= 0 , default = 0 )
):
self .user_id = user_id
self .email = email
self .password_hash = password_hash
self .prefs = prefs
self .age = age
# Encode a user
user = User( email = "[email protected] " , user_id = 123 , password_hash = "secret" )
print (json.dumps(user))
# {"id": 123, "email": "[email protected] ", "prefs": {}, "age": 0}
# Decode a user
user_data = '{"id": 456, "email": "[email protected] "}'
user = json.loads(User, user_data)
print (user.user_id) # 456
print (user.prefs) # {}
Field Options Reference
Use a different name for the field in serialized output
Exclude the field from serialized output
Default value if the field is missing during deserialization
Zero-argument function to create default values (for mutable defaults)
Custom function to encode the field’s value
Custom function to decode the field’s value
validate
callable | list[callable]
Function or list of functions to validate the field during deserialization
Built-in Validators
Lodum includes several built-in validators in the lodum.validators module:
from lodum import lodum, field, json
from lodum.validators import Range, Length, Match, OneOf
@lodum
class Product :
def __init__ (
self ,
name : str = field( validate = Length( min = 3 , max = 50 )),
price : float = field( validate = Range( min = 0 )),
category : str = field( validate = OneOf([ "electronics" , "books" , "clothing" ])),
code : str = field( validate = Match( r " ^ [ A-Z ] {2} - \d {4} $ " ))
):
self .name = name
self .price = price
self .category = category
self .code = code
# Valid product
product = json.loads(
Product,
'{"name": "Laptop", "price": 999.99, "category": "electronics", "code": "EL-1234"}'
)
# This will raise DeserializationError
try :
json.loads(Product, '{"name": "A", "price": -10, "category": "food", "code": "abc"}' )
except Exception as e:
print (e) # Length validation error
Available Validators
Range - Validate numeric ranges
from lodum.validators import Range
# Minimum only
field( validate = Range( min = 0 ))
# Maximum only
field( validate = Range( max = 100 ))
# Both min and max
field( validate = Range( min = 18 , max = 120 ))
Length - Validate string/collection length
from lodum.validators import Length
# Minimum length
field( validate = Length( min = 3 ))
# Maximum length
field( validate = Length( max = 50 ))
# Both
field( validate = Length( min = 3 , max = 20 ))
Match - Validate with regex pattern
from lodum.validators import Match
# Email pattern
field( validate = Match( r " ^ [ a-z ] + @ [ a-z ] + \. [ a-z ] + $ " ))
# Product code
field( validate = Match( r " ^ [ A-Z ] {2} - \d {4} $ " ))
OneOf - Validate against allowed values
from lodum.validators import OneOf
# String options
field( validate = OneOf([ "admin" , "user" , "guest" ]))
# Numeric options
field( validate = OneOf([ 1 , 2 , 3 , 5 , 8 , 13 ]))
Custom Validators
Create custom validators as simple functions:
from lodum.exception import DeserializationError
def is_even ( v ):
if v % 2 != 0 :
raise DeserializationError( "Value must be even" )
@lodum
class EvenOnly :
def __init__ ( self , val : int = field( validate = is_even)):
self .val = val
json.loads(EvenOnly, '{"val": 4}' ) # Success
json.loads(EvenOnly, '{"val": 5}' ) # DeserializationError
Multiple Validators
Apply multiple validators to a single field:
@lodum
class MultiValidated :
def __init__ ( self , val : int = field( validate = [Range( min = 10 ), Range( max = 20 )])):
self .val = val
json.loads(MultiValidated, '{"val": 15}' ) # Success
json.loads(MultiValidated, '{"val": 5}' ) # Fails min check
json.loads(MultiValidated, '{"val": 25}' ) # Fails max check
Complex Type Support
Lodum supports a wide variety of Python types out of the box.
Enums
from enum import Enum
class UserRole ( Enum ):
ADMIN = "admin"
USER = "user"
GUEST = "guest"
@lodum
class User :
def __init__ ( self , name : str , role : UserRole):
self .name = name
self .role = role
user = User( name = "Alice" , role = UserRole. ADMIN )
json_str = json.dumps(user)
# {"name": "Alice", "role": "admin"}
restored = json.loads(User, json_str)
assert restored.role == UserRole. ADMIN
Datetime Objects
Datetime objects are encoded as ISO 8601 strings:
from datetime import datetime
@lodum
class Event :
def __init__ ( self , created_at : datetime, name : str ):
self .created_at = created_at
self .name = name
event = Event( created_at = datetime( 2024 , 3 , 15 , 10 , 30 ), name = "Meeting" )
json_str = json.dumps(event)
# {"created_at": "2024-03-15T10:30:00", "name": "Meeting"}
Sets
@lodum
class Permissions :
def __init__ ( self , roles : set[ str ]):
self .roles = roles
perms = Permissions( roles = { "read" , "write" , "delete" })
json_str = json.dumps(perms)
restored = json.loads(Permissions, json_str)
assert isinstance (restored.roles, set )
Optional and Union Types
from typing import Optional, Union
@lodum
class FlexibleData :
def __init__ (
self ,
optional_field : Optional[ int ],
union_field : Union[ str , bool , int ],
any_field : Any
):
self .optional_field = optional_field
self .union_field = union_field
self .any_field = any_field
data = FlexibleData(
optional_field = None ,
union_field = "hello" ,
any_field = [ 1 , 2 , { "key" : "value" }]
)
Generic Types
from typing import Generic, TypeVar
T = TypeVar( "T" )
@lodum
class Container (Generic[T]):
def __init__ ( self , value : T):
self .value = value
int_container = Container( value = 42 )
str_container = Container( value = "hello" )
list_container = Container( value = [ 1 , 2 , 3 ])
Binary Data Handling
Lodum handles binary data differently based on the format:
@lodum
class BinaryData :
def __init__ ( self , data : bytes ):
self .data = data
binary = BinaryData( data = b " \x00\x01\x02\x03 " )
# Text formats (JSON, TOML) use Base64 encoding
json_output = json.dumps(binary)
# {"data": "AAECAw=="}
# Binary formats (MsgPack, CBOR) use native binary
msgpack_output = msgpack.dumps(binary)
Text-based formats (JSON, TOML) encode binary data as Base64 strings.Binary formats (MsgPack, CBOR, BSON, Pickle) and YAML use native binary representation for efficient storage.
Collection Wrappers
Lodum automatically normalizes standard library collection wrappers:
from collections import deque, defaultdict, Counter, OrderedDict
@lodum
class Collections :
def __init__ (
self ,
queue : deque,
counts : Counter,
defaults : defaultdict,
ordered : OrderedDict
):
self .queue = queue
self .counts = counts
self .defaults = defaults
self .ordered = ordered
These are automatically converted to/from standard list and dict during serialization.
Custom Serializers and Deserializers
Implement custom encoding/decoding logic for specific fields:
from datetime import datetime
def serialize_timestamp ( dt : datetime) -> int :
return int (dt.timestamp())
def deserialize_timestamp ( ts : int ) -> datetime:
return datetime.fromtimestamp(ts)
@lodum
class Event :
def __init__ (
self ,
timestamp : datetime = field(
serializer = serialize_timestamp,
deserializer = deserialize_timestamp
)
):
self .timestamp = timestamp
Next Steps
Streaming Learn about O(1) memory streaming for large datasets
Schema Generation Generate JSON schemas from your data models