Skip to main content

Overview

The field() function provides metadata to control how individual fields are serialized, deserialized, and validated. It returns a Field object that should be used as the default value in your class’s __init__ method.

Signature

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

Parameters

rename
str | None
default:"None"
The name to use for this field in the serialized output. Useful for matching external API conventions (e.g., camelCase) while keeping Python naming conventions (snake_case).
skip_serializing
bool
default:"False"
If True, the field will be excluded from serialized output. The field can still be set during initialization and used in your code, but won’t appear in JSON, YAML, etc.
default
Any
default:"_MISSING"
A default value to use if the field is missing during deserialization. Cannot be used together with default_factory.
default_factory
Callable[[], Any] | None
default:"None"
A zero-argument callable that returns a default value. Called each time a default is needed (useful for mutable defaults like lists). Cannot be used together with default.
serializer
Callable[[Any], Any] | None
default:"None"
A custom function to transform the field’s value during serialization. Receives the field value and should return the serialized representation.
deserializer
Callable[[Any], Any] | None
default:"None"
A custom function to transform the field’s value during deserialization. Receives the raw value and should return the typed instance.
validate
Callable[[Any], None] | List[Callable[[Any], None]] | None
default:"None"
A validation function or list of validation functions. Each function receives the field value during deserialization and should raise an exception if validation fails.

Returns

A Field object containing the specified metadata. This should be used as the default value in your __init__ parameter.

Basic Usage

Renaming Fields

from lodum import lodum, field

@lodum
class User:
    def __init__(
        self,
        user_id: int = field(rename="userId"),
        first_name: str = field(rename="firstName"),
    ):
        self.user_id = user_id
        self.first_name = first_name

# Serializes to: {"userId": 123, "firstName": "John"}

Default Values

from lodum import lodum, field
from typing import List

@lodum
class Config:
    def __init__(
        self,
        timeout: int = field(default=30),
        tags: List[str] = field(default_factory=list),
        enabled: bool = field(default=True),
    ):
        self.timeout = timeout
        self.tags = tags
        self.enabled = enabled

Skipping Fields

from lodum import lodum, field

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

# Only "username" appears in serialized output

Advanced Usage

Custom Serializers and Deserializers

from lodum import lodum, field
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,
        name: str,
        created_at: datetime = field(
            serializer=serialize_timestamp,
            deserializer=deserialize_timestamp,
        ),
    ):
        self.name = name
        self.created_at = created_at

Validation

from lodum import lodum, field

def validate_positive(value: int) -> None:
    if value <= 0:
        raise ValueError("Must be positive")

def validate_range(value: int) -> None:
    if not (1 <= value <= 100):
        raise ValueError("Must be between 1 and 100")

@lodum
class Score:
    def __init__(
        self,
        points: int = field(validate=[validate_positive, validate_range]),
    ):
        self.points = points

Combining Options

from lodum import lodum, field
from typing import Optional

@lodum
class Product:
    def __init__(
        self,
        product_id: str = field(rename="id"),
        price: float = field(
            rename="priceUSD",
            validate=lambda x: x >= 0 if x else None,
        ),
        internal_code: Optional[str] = field(
            default=None,
            skip_serializing=True,
        ),
    ):
        self.product_id = product_id
        self.price = price
        self.internal_code = internal_code

Validation Details

Validation functions are called during deserialization in the order they appear in the list. If any validation function raises an exception, deserialization fails immediately.
def validate_email(email: str) -> None:
    if "@" not in email:
        raise ValueError(f"Invalid email: {email}")

@lodum
class Contact:
    def __init__(
        self,
        email: str = field(validate=validate_email),
    ):
        self.email = email

Default Factory Patterns

Use default_factory for mutable defaults to avoid sharing instances:
from lodum import lodum, field
from typing import List, Dict

@lodum
class Container:
    def __init__(
        self,
        items: List[str] = field(default_factory=list),  # Correct
        metadata: Dict[str, str] = field(default_factory=dict),  # Correct
    ):
        self.items = items
        self.metadata = metadata

# Each instance gets its own list and dict

Notes

  • default and default_factory are mutually exclusive - you can only specify one
  • All parameters are keyword-only (must use field(rename="..."), not field("..."))
  • Validation only runs during deserialization, not when setting attributes directly
  • Custom serializers/deserializers override the default type handling
  • The Field object itself should not be instantiated directly - always use the field() function

See Also

Build docs developers (and LLMs) love