Aliases, computed fields and serializers

Pydantic · Practical · 14 min

External JSON rarely matches Python naming, and outputs often need derived or reformatted values.

Aliases decouple the attribute name from the external key.

  • Field(alias="userId") sets the name for both validation and serialization (when you dump with by_alias=True).
  • validation_alias and serialization_alias set each direction separately. validation_alias=AliasChoices("user_id", "userId") accepts either spelling, and AliasPath("data", 0, "id") digs into nested input.
  • model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) (with to_camel from pydantic.alias_generators) gives every field a camelCase alias while still allowing snake_case input.

By default, input must use the alias once one is set, and dumps use field names unless you pass by_alias=True (or configure serialize_by_alias=True).

Computed fields are properties included in dumps and JSON Schema:

@computed_field
@property
def total(self) -> float: ...

They are read-only and recomputed on each access; annotate the return type so the schema knows it.

Custom serializers change how a field is written without changing how it is stored. @field_serializer("amount") returns the output value; for example, a Decimal as a string to avoid float rounding. when_used="json" limits it to JSON mode. @model_serializer takes over the whole model's output.

Keep these output concerns on the model rather than post-processing dicts in every endpoint: one definition, consistent everywhere.

Example

from decimal import Decimal
from pydantic import BaseModel, ConfigDict, computed_field, field_serializer
from pydantic.alias_generators import to_camel

class Invoice(BaseModel):
    model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
    invoice_id: int
    unit_price: Decimal
    quantity: int

    @computed_field
    @property
    def total_price(self) -> Decimal:
        return self.unit_price * self.quantity

    @field_serializer("unit_price", "total_price", when_used="json")
    def money(self, v: Decimal) -> str:
        return f"{v:.2f}"

inv = Invoice.model_validate({"invoiceId": 9, "unitPrice": "2.5", "quantity": 3})
print(inv.invoice_id, inv.total_price)          # 9 7.5
print(inv.model_dump_json(by_alias=True))
# {"invoiceId":9,"unitPrice":"2.50","quantity":3,"totalPrice":"7.50"}
print(Invoice(invoice_id=1, unit_price=1, quantity=1).invoice_id)   # 1 (by name)

Remember

  • alias, validation_alias and serialization_alias map external names
  • alias_generator=to_camel plus populate_by_name handles camelCase APIs
  • @computed_field exposes derived values in dumps and schemas
  • @field_serializer customizes output without changing stored values

Watch out for

  • Expecting dumps to use aliases by default; pass by_alias=True or set serialize_by_alias.

Check yourself

  1. A field has Field(alias="userId"). Without other config, which input works?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.pydantic.dev/latest/concepts/alias/