Skip to main content
The field() function allows you to customize how individual class attributes are serialized and deserialized. Use it as a default value in your __init__ method.

Basic Usage

from lodum import lodum, field, json

@lodum
class User:
    def __init__(
        self,
        email: str,
        user_id: int = field(rename="id", default=0),
        password_hash: str = field(skip_serializing=True, default=""),
        preferences: dict = field(default_factory=dict),
    ):
        self.email = email
        self.user_id = user_id
        self.password_hash = password_hash
        self.preferences = preferences

user = User(email="[email protected]", user_id=123, password_hash="secret")

print(json.dumps(user))
# {"email": "[email protected]", "id": 123, "preferences": {}}
# Note: password_hash is excluded, user_id becomes "id"

Field Options

rename

Change the field name in the serialized output:
from lodum import lodum, field, json

@lodum
class Product:
    def __init__(
        self,
        product_id: int = field(rename="id"),
        product_name: str = field(rename="name"),
    ):
        self.product_id = product_id
        self.product_name = product_name

product = Product(product_id=42, product_name="Widget")

print(json.dumps(product))
# {"id": 42, "name": "Widget"}

# Deserialization uses the renamed field
product = json.loads(Product, '{"id": 99, "name": "Gadget"}')
print(product.product_id)  # 99

skip_serializing

Exclude a field from serialization output:
from lodum import lodum, field, json

@lodum
class Account:
    def __init__(
        self,
        username: str,
        password: str = field(skip_serializing=True, default=""),
        api_key: str = field(skip_serializing=True, default=""),
    ):
        self.username = username
        self.password = password
        self.api_key = api_key

account = Account(username="alice", password="secret123", api_key="key_abc")

print(json.dumps(account))
# {"username": "alice"}
# Sensitive fields are excluded
Fields with skip_serializing=True are not included in the output, but can still be deserialized if present in the input. For security-critical use cases, ensure sensitive data is never sent to the client.

default

Provide a default value for missing fields during deserialization:
from lodum import lodum, field, json

@lodum
class Settings:
    def __init__(
        self,
        theme: str = field(default="light"),
        timeout: int = field(default=30),
        debug: bool = field(default=False),
    ):
        self.theme = theme
        self.timeout = timeout
        self.debug = debug

# Missing fields use defaults
settings = json.loads(Settings, '{"theme": "dark"}')
print(settings.timeout)  # 30
print(settings.debug)    # False

default_factory

Use a callable to generate default values (important for mutable defaults):
from lodum import lodum, field, json

@lodum
class Task:
    def __init__(
        self,
        name: str,
        tags: list[str] = field(default_factory=list),
        metadata: dict = field(default_factory=dict),
    ):
        self.name = name
        self.tags = tags
        self.metadata = metadata

task1 = Task(name="Task 1")
task2 = Task(name="Task 2")

task1.tags.append("urgent")
print(task2.tags)  # [] - separate list instances
Always use default_factory for mutable defaults like list, dict, or set to avoid sharing instances between objects.

serializer

Custom function to transform values during serialization:
from lodum import lodum, field, json
from datetime import datetime

@lodum
class Event:
    def __init__(
        self,
        name: str,
        created_at: datetime = field(
            serializer=lambda dt: dt.isoformat(),
            default_factory=datetime.now,
        ),
    ):
        self.name = name
        self.created_at = created_at

event = Event(name="Launch", created_at=datetime(2026, 3, 3, 12, 0, 0))

print(json.dumps(event))
# {"name": "Launch", "created_at": "2026-03-03T12:00:00"}
Lodum has built-in support for datetime.datetime, so this example is illustrative. The library automatically converts datetimes to ISO 8601 strings.

deserializer

Custom function to transform values during deserialization:
from lodum import lodum, field, json
from datetime import datetime

@lodum
class Event:
    def __init__(
        self,
        name: str,
        created_at: datetime = field(
            serializer=lambda dt: dt.isoformat(),
            deserializer=lambda s: datetime.fromisoformat(s),
        ),
    ):
        self.name = name
        self.created_at = created_at

event_data = '{"name": "Launch", "created_at": "2026-03-03T12:00:00"}'
event = json.loads(Event, event_data)

print(type(event.created_at))  # <class 'datetime.datetime'>
print(event.created_at.year)   # 2026

validate

Add validation functions to ensure data integrity. See the Validation System guide for details.
from lodum import lodum, field, json

def positive(value):
    if value <= 0:
        raise ValueError("Must be positive")

@lodum
class Product:
    def __init__(
        self,
        name: str,
        price: float = field(validate=positive),
    ):
        self.name = name
        self.price = price

# This raises DeserializationError
try:
    json.loads(Product, '{"name": "Widget", "price": -10}')
except Exception as e:
    print(e)  # Error at price: Must be positive

Field Type Signature

From src/lodum/field.py:107-143:
def field(
    *,
    rename: Optional[str] = None,
    skip_serializing: bool = False,
    default: Any = _MISSING,
    default_factory: Optional[Callable[[], Any]] = None,
    serializer: Optional[Callable[[Any], Any]] = None,
    deserializer: Optional[Callable[[Any], Any]] = None,
    validate: Optional[
        Union[Callable[[Any], None], List[Callable[[Any], None]]]
    ] = None,
) -> Any:
    """
    Provides metadata to the `@lodum` decorator for a single field.

    Args:
        rename: The name to use for the field in the output.
        skip_serializing: If `True`, the field will not be included in the
            output.
        default: A default value to use for the field during decoding
            if it is missing from the input data.
        default_factory: A zero-argument function that will be called to
            create a default value for a missing field.
        serializer: A function to call to encode the field's value.
        deserializer: A function to call to decode the field's value.
        validate: A callable or list of callables to validate the field's value during decoding.
    """

The Field Class

Internally, field() returns a Field instance from src/lodum/field.py:27-105:
class Field:
    """
    A class that stores metadata for a field in a lodum-enabled class.

    This is not intended to be instantiated directly. Instead, use the `field()`
    function, which provides a more convenient API.
    """

    def __init__(
        self,
        rename: Optional[str] = None,
        skip_serializing: bool = False,
        default: Any = _MISSING,
        default_factory: Optional[Callable[[], Any]] = None,
        serializer: Optional[Callable[[Any], Any]] = None,
        deserializer: Optional[Callable[[Any], Any]] = None,
        validate: Optional[
            Union[Callable[[Any], None], List[Callable[[Any], None]]]
        ] = None,
    ) -> None:
        if default is not _MISSING and default_factory is not None:
            raise ValueError("cannot specify both default and default_factory")

        self.rename = rename
        self.skip_serializing = skip_serializing
        self.default = default
        self.default_factory = default_factory
        self.serializer = serializer
        self.deserializer = deserializer
        self.validate = validate
        self.name: str = ""  # Will be populated by the decorator
        self.type: Any = None  # Will be populated by the decorator

    @property
    def has_default(self) -> bool:
        return self.default is not _MISSING or self.default_factory is not None

    def get_default(self) -> Any:
        if self.default_factory is not None:
            return self.default_factory()
        return self.default

Advanced Example

Combine multiple field options:
from lodum import lodum, field, json
from lodum.validators import Length, Range
from datetime import datetime

@lodum
class BlogPost:
    def __init__(
        self,
        # Renamed field with validation
        post_id: int = field(rename="id", validate=Range(min=1)),
        
        # Required field with length validation  
        title: str = field(validate=Length(min=1, max=200)),
        
        # Field with custom serializer/deserializer
        published_at: datetime = field(
            rename="published",
            serializer=lambda dt: dt.isoformat(),
            deserializer=lambda s: datetime.fromisoformat(s),
        ),
        
        # Optional field with factory default
        tags: list[str] = field(default_factory=list),
        
        # Private field excluded from output
        edit_token: str = field(skip_serializing=True, default=""),
    ):
        self.post_id = post_id
        self.title = title
        self.published_at = published_at
        self.tags = tags
        self.edit_token = edit_token

post = BlogPost(
    post_id=1,
    title="Hello Lodum",
    published_at=datetime(2026, 3, 3),
    tags=["python", "serialization"],
    edit_token="secret_abc123",
)

print(json.dumps(post, indent=2))
# {
#   "id": 1,
#   "title": "Hello Lodum",
#   "published": "2026-03-03T00:00:00",
#   "tags": ["python", "serialization"]
# }

Field Parameters Summary

ParameterTypeDefaultDescription
renamestr | NoneNoneSerialized field name
skip_serializingboolFalseExclude from output
defaultAny_MISSINGDefault value if missing
default_factoryCallable[[], Any] | NoneNoneFunction to create default
serializerCallable[[Any], Any] | NoneNoneCustom encode function
deserializerCallable[[Any], Any] | NoneNoneCustom decode function
validateCallable | List[Callable] | NoneNoneValidation function(s)

Constraints

Cannot specify both default and default_factory. The Field constructor will raise ValueError if you try.
# This raises ValueError
field(default=[], default_factory=list)  # Error!

# Use one or the other
field(default=[])           # OK for immutable
field(default_factory=list) # OK for mutable

Build docs developers (and LLMs) love