FastAPI Request Body 巢狀模型與複雜資料結構 (Nested Models & Complex Data Structures)
隨著 API 功能越來越複雜,你傳輸的 JSON 資料結構往往不會只是扁平的鍵值對。你可能會有物件裡面包物件(巢狀結構),或是由物件組成的列表 (List of Objects)。
FastAPI 透過 Pydantic 強大的型別系統,可以輕鬆處理任意深度的複雜資料結構。
List 欄位 (列表)
要定義一個欄位為列表,可以使用 Python 標準庫 typing 中的 List (Python 3.9+ 可以直接用 list,但為了相容性與明確性,範例使用 List)。
from typing import Annotated
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
# tags 是一個字串列表,預設為空列表
tags: list[str] = []
@app.post("/items/")
async def create_item(item: Item):
return item
在 Python 3.9+ 中,你可以直接使用內建的
list[str] 而不需要從 typing 導入 List。同樣地,Python 3.10+ 支援使用 | None 來取代 Optional,這讓程式碼更加簡潔。Request Body 範例:
{
"name": "Foo",
"price": 42.0,
"tags": ["rock", "metal", "bar"]
}
Set 型別 (集合)
如果你希望列表中的元素是不重複的,可以使用 Set。
class Item(BaseModel):
name: str
price: float
tags: Set[str] = set()
如果傳入 ["foo", "bar", "foo"],FastAPI (Pydantic) 會自動去重,變成 {"foo", "bar"}。
巢狀模型 (Nested Models)
你可以將一個 Pydantic 模型用作另一個模型的欄位型別。
from typing import Optional
from pydantic import BaseModel
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tags: Set[str] = set()
# image 欄位本身就是一個 Image 物件
image: Optional[Image] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Request Body 範例:
{
"name": "Foo",
"price": 35.4,
"tags": ["offer"],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
FastAPI 能夠自動解析並驗證內部的 image 物件是否符合 Image 模型的定義。
特殊型別與驗證
Pydantic 支援許多特殊的複雜型別,例如 URL 驗證。
from pydantic import BaseModel, HttpUrl
class Image(BaseModel):
url: HttpUrl # 自動驗證是否為合法的 HTTP/HTTPS URL
name: str
如果你傳入 "url": "not a url",會直接報錯,拒絕請求。
含有巢狀模型的列表
你可以結合 List 和 Model,定義一個「物件列表」。
class Item(BaseModel):
name: str
price: float
# images 是一個 Image 物件的列表
images: List[Image] = []
Request Body 範例:
{
"name": "Foo",
"price": 32.5,
"images": [
{
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
},
{
"url": "http://example.com/dave.jpg",
"name": "The Baz"
}
]
}
深層巢狀結構
你可以定義任意深度的巢狀結構:
class Offer(BaseModel):
name: str
description: Optional[str] = None
price: float
items: List[Item] # Offer 包含多個 Item,Item 又包含多個 Image
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
Dict 型別 (字典)
如果你不知道鍵 (Key) 的名稱,但知道值 (Value) 的型別,可以使用 Dict。
from typing import Dict
class Item(BaseModel):
name: str
# weights 是一個字典,Key 是 int,Value 是 float
weights: Dict[int, float]
Request Body 範例:
{
"name": "Foo",
"weights": {
"1": 0.5,
"2": 0.3
}
}
注意:JSON 的 Key 永遠是字串,但因為我們定義 Dict[int, float],Pydantic 會嘗試將 Key "1" 轉為整數 1。
總結
- 利用
List,Set,Dict等標準型別宣告複雜結構。 - 將 Pydantic Model 作為欄位型別,即可建立巢狀模型。
- 可以組合使用,例如
List[YourModel]。 - Pydantic 會自動遞迴地驗證所有深層資料。