Cheatsheet for mapping between Python field names and external API names.
Basic alias
class User(BaseModel):
full_name: str = Field(alias="fullName")
model_config = {"populate_by_name": True}
# Both accepted:
User.model_validate({"fullName": "Alice"})
User.model_validate({"full_name": "Alice"})
# Serialization
User(full_name="Alice").model_dump() # {"full_name": "Alice"}
User(full_name="Alice").model_dump(by_alias=True) # {"fullName": "Alice"}
validation_alias vs serialization_alias
class User(BaseModel):
user_id: int = Field(validation_alias="id", serialization_alias="userId")
# Input: {"id": 42}
# Output (with by_alias=True): {"userId": 42}
For asymmetric mappings.
AliasChoices (try multiple)
from pydantic import AliasChoices
class User(BaseModel):
name: str = Field(validation_alias=AliasChoices("name", "fullName", "full_name"))
User.model_validate({"fullName": "Alice"}) # works
User.model_validate({"full_name": "Alice"}) # works
User.model_validate({"name": "Alice"}) # works
Tries each in order.
AliasPath (nested input)
from pydantic import AliasPath
class User(BaseModel):
user_id: int = Field(validation_alias=AliasPath("data", "user", "id"))
User.model_validate({"data": {"user": {"id": 42}}})
For unwrapping nested external JSON.
AliasChoices + AliasPath
class User(BaseModel):
user_id: int = Field(validation_alias=AliasChoices(
AliasPath("data", "user", "id"),
"user_id",
"id",
))
populate_by_name
class User(BaseModel):
full_name: str = Field(alias="fullName")
model_config = {"populate_by_name": True}
Without populate_by_name: only the alias is accepted (not the Python name).
Global alias generator
from pydantic.alias_generators import to_camel
class User(BaseModel):
full_name: str
email_address: str
model_config = {
"alias_generator": to_camel, # full_name → fullName
"populate_by_name": True,
}
User.model_validate({"fullName": "Alice", "emailAddress": "[email protected]"})
For consistent snake_case ↔ camelCase mapping.
Custom alias generator
def custom_gen(field_name: str) -> str:
return f"x_{field_name}"
class M(BaseModel):
name: str
model_config = {"alias_generator": custom_gen, "populate_by_name": True}
Per-field override
class User(BaseModel):
full_name: str = Field(alias="fullName") # explicit
age: int # uses alias_generator
model_config = {"alias_generator": to_camel, "populate_by_name": True}
Field-level alias takes precedence.
Round-trip
user = User.model_validate({"fullName": "Alice"})
data = user.model_dump(by_alias=True)
restored = User.model_validate(data)
by_alias=True outputs aliases; round-trip works.
Use cases
External API adapter
class StripeCustomer(BaseModel):
id: str
email: str
created: int = Field(serialization_alias="created_at_unix")
metadata_: dict = Field(alias="metadata")
snake_case backend ↔ camelCase frontend
class UserOut(BaseModel):
full_name: str
email_address: str
model_config = {"alias_generator": to_camel, "populate_by_name": True}
# API returns {"fullName": "...", "emailAddress": "..."}
Reserved-word avoidance
class M(BaseModel):
type_: str = Field(alias="type") # 'type' is built-in; trailing underscore + alias
OpenAPI generation
Aliases reflected in JSON Schema with validation_alias. FastAPI uses for the OpenAPI spec.
Common mistakes
- Forgetting
populate_by_name=True— Python attribute access works, but model_validate by Python name fails. - Inconsistent
by_aliasin serialization across endpoints. - AliasGenerator on inherited models without
populate_by_name— confusion.
Read this next
If you want my snake↔camel API adapter pattern, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .