跳轉至

建立 2026-10-11 更新 2026-10-11

巢狀模型

巢狀模型(nested models)是把一個 Pydantic 模型當作另一個模型的欄位型別,用來描述多層的 JSON 結構。Pydantic 會遞迴驗證整棵資料樹,錯誤訊息會附上完整路徑。本頁介紹基本巢狀、模型串列、自我參照的遞迴結構,以及用 Discriminated Union 依欄位值選擇不同模型。

如何讓模型包含另一個模型?

直接把模型當作型別使用即可,傳入 dict 會自動轉成對應的模型物件:

from pydantic import BaseModel


class Address(BaseModel):
    city: str
    street: str


class Item(BaseModel):
    name: str
    price: float
    quantity: int = 1


class Order(BaseModel):
    id: int
    shipping: Address
    items: list[Item]

    @property
    def total(self) -> float:
        return sum(i.price * i.quantity for i in self.items)


data = {
    "id": 1001,
    "shipping": {"city": "台北市", "street": "忠孝東路一段 1 號"},
    "items": [
        {"name": "鍵盤", "price": 1290},
        {"name": "滑鼠", "price": 590, "quantity": 2},
    ],
}

order = Order.model_validate(data)
print(order.shipping.city)  # 台北市
print(order.items[1].name)  # 滑鼠
print(order.total)          # 2470.0

model_dump() 也會遞迴把所有子模型轉回 dict。

巢狀資料出錯時,怎麼知道是哪一筆?

錯誤的 loc 會包含完整路徑,串列會顯示索引:

from pydantic import BaseModel, ValidationError


class Item(BaseModel):
    name: str
    price: float


class Cart(BaseModel):
    items: list[Item]


try:
    Cart.model_validate({"items": [{"name": "A", "price": 10}, {"name": "B", "price": "免費"}]})
except ValidationError as e:
    print(e.errors()[0]["loc"])  # ('items', 1, 'price')

讀法:items 串列的第 2 筆(索引 1)的 price 欄位有問題。

樹狀或遞迴的結構要怎麼定義?

模型可以參照自己,例如目錄樹或留言串。用字串寫型別名稱(前向參照,forward reference)即可:

from pydantic import BaseModel


class Comment(BaseModel):
    author: str
    text: str
    replies: list["Comment"] = []


thread = Comment.model_validate({
    "author": "A",
    "text": "Pydantic 好用嗎?",
    "replies": [{"author": "B", "text": "很好用", "replies": [{"author": "A", "text": "感謝"}]}],
})
print(thread.replies[0].replies[0].text)  # 感謝

一個欄位可能是不同模型時怎麼辦?

例如付款方式可能是信用卡或轉帳,兩者欄位不同。最好的做法是加一個 Literal 標記欄位,並用 Field(discriminator=...) 告訴 Pydantic 依它選模型。這叫 Discriminated Union(帶標記的聯集),比讓 Pydantic 逐一嘗試更快、錯誤訊息也更清楚:

from typing import Literal

from pydantic import BaseModel, Field


class CreditCard(BaseModel):
    method: Literal["card"]
    card_number: str


class BankTransfer(BaseModel):
    method: Literal["transfer"]
    bank_code: str
    account: str


class Payment(BaseModel):
    amount: float
    detail: CreditCard | BankTransfer = Field(discriminator="method")


p = Payment.model_validate({"amount": 500, "detail": {"method": "transfer", "bank_code": "004", "account": "123"}})
print(type(p.detail).__name__)  # BankTransfer

推薦影音

James Clare:Nested Models & Complex JSON

簡述:約 11 分鐘,示範如何用巢狀模型表示複雜的 JSON 結構,適合想練習「把 API 回應轉成模型」的讀者。巢狀模型在 FastAPI 中的用法,可再看 FastAPI 整合 頁的 BugBytes 影片。

相關資料