I have a model, called Candidate that I'm trying to return, two of the fields refer to another model, StaffMember. These are originally coming from my database/ORM as sqlalchemy responses.
class StaffMember(BaseModel):
fname: str
sname: str
@computed_field
@property
def full_name(self) -> str:
return f"{self.fname} {self.sname}"
model_config = ConfigDict(from_attributes=True)
class CandidateResponse(BaseModel):
id: int
fname: str
sname: str
dob: date
created_on: datetime
created_by: StaffMember
updated_on: datetime
updated_by: StaffMember
model_config = ConfigDict(from_attributes=True)
I want to return a CandidateResponse dict/JSON as a single-level. Normally, the response looks like this:
{
"id": 2,
"fname": "Joe01",
"sname": "Bloggs",
"dob": "2004-12-31",
"created_on": "2025-02-17T09:34:27",
"created_by": {
"fname": "System",
"sname": "Default",
"full_name": "System Default"
},
"updated_on": "2025-02-17T09:34:27",
"updated_by": {
"fname": "System",
"sname": "Default",
"full_name": "System Default"
}
}
However, I don't want any nested fields. Is there a way to do this? - ideally without having to define all the fields again.
I'm looking for a response more like:
{
"id": 2,
"fname": "Joe01",
"sname": "Bloggs",
"dob": "2004-12-31",
"created_on": "2025-02-17T09:34:27",
"created_by_fname": "System",
"created_by_sname": "Default",
"created_by_full_name": "System Default",
"updated_on": "2025-02-17T09:34:27",
"updated_by_fname": "System",
"updated_by_sname": "Default",
"updated_by_full_name": "System Default"
}
I could use field_serializer but I feel like that is a messy solution as I would multiple computed fields (per nested model) to create.
I would also ask:
You can use @model_serializer
to achieve this:
from collections.abc import Generator
from typing import Any
from pydantic import BaseModel, model_serializer
class FooModel(BaseModel):
subkey: str
class BarModel(BaseModel):
key: FooModel
otherkey: int
@model_serializer()
def serialize_model(self):
return {k: v for k, v in model_iter(self)}
def model_iter(model: BaseModel) -> Generator[tuple[str, Any], None, None]:
for field, value in model:
if isinstance(value, BaseModel):
for key, val in model_iter(value):
yield f"{field}_{key}", val
else:
yield field, value
bar = BarModel(otherkey=1, key=FooModel(subkey="hello"))
print(bar.model_dump()) # {'key_subkey': 'hello', 'otherkey': 1}
This will work as a FastAPI response model, but will still generate an unnested JSON schema in the OpenAPI documentation, but that can probably be achieved with some extra effort, if required.