FastAPI 嵌套模型实战指南:用 Pydantic 构建任意深度的请求体结构
本篇技术指南基于 FastAPI 官方教程中“Body – 嵌套模型(verschachtelte Modelle)”一章展开,系统讲解如何利用 Pydantic 在 FastAPI 中定义、校验和文档化任意深度的嵌套 JSON 请求体——包括带类型参数的列表、去重集合、子模型、list[子模型]、纯列表请求体以及任意键类型的 dict 请求体。读完后,你将能够编写出“声明即校验、声明即文档”的复杂请求体处理代码,并理解 FastAPI 背后的参数解析与测试验证机制。
为什么嵌套模型是 FastAPI 的核心能力
借助 Pydantic,FastAPI 允许你定义任意深度的嵌套模型,并对它们进行校验、类型转换与自动文档生成。官方教程中所有示例代码都位于 docs_src/body_nested_models/ 目录,对应的自动化测试则位于 tests/test_tutorial/test_body_nested_models/ 目录(包含 test_tutorial001_tutorial002_tutorial003.py、test_tutorial004.py 至 test_tutorial009.py 等文件),可运行测试验证每一段示例代码的真实行为。
一、列表作为字段:从裸 list 起步
最简单的场景是把某个属性声明为 Python 的 list 类型。以 tutorial001_py310.py 为例:
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: list = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
这里 tags: list = [] 表示 tags 是一个列表,但没有约束列表内部元素的类型——它可以是任意 JSON 值。同时注意 tags 的默认值是空列表,因此它是可选字段。端点函数签名 update_item(item_id: int, item: Item) 中,item 的注解是 Pydantic 模型,FastAPI 会自动将其解析为请求体(而非路径或查询参数)。
二、带类型参数的列表:list[str]
Python 提供了声明“内部类型”(即类型参数)的标准语法,适用于 list、dict、tuple 等容器类型——把内部类型放在方括号 [ 和 ] 中:
my_list: list[str]
这是纯标准 Python 类型声明语法,对模型属性同样适用。在 tutorial002_py310.py 中,我们把 tags 收紧为“字符串列表”:
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
这一行改动带来的差异:
| 声明 | 语义 | 非法输入示例 |
|---|---|---|
tags: list |
元素类型不受限 | 无法拒绝 |
tags: list[str] |
每个元素必须是字符串(或可转为字符串) | {"tags": [123.4, null]} 会触发 422 校验错误 |
对应的测试 test_tutorial001_tutorial002_tutorial003.py 会用 TestClient 实际发送包含非法元素类型的请求体,验证 FastAPI 返回 422。
三、set 类型:自动去重
标签(tags)通常不应重复,Python 专门用于唯一元素集合的数据类型是 set。在 tutorial003_py310.py 中:
tags: set[str] = set()
这带来三个关键行为:
- 入站去重:即使客户端在请求中发送了重复数据(如
{"tags": ["rock", "rock", "metal"]}),FastAPI 也会将其转换为唯一元素的集合; - 出站稳定输出:无论源头是否有重复,序列化输出时始终是去重后的集合;
- 文档同步:OpenAPI 文档中会相应标注为
uniqueItems的数组类型,自动文档与实际行为一致。
四、嵌套模型:用子模型描述深层 JSON 对象
Pydantic 模型的每个属性都有类型,而这个类型本身可以是另一个 Pydantic 模型。这样你就能声明带特定属性名、类型和校验规则的深层嵌套 JSON “对象”,且嵌套深度不受限制。
4.1 定义子模型
在 tutorial004_py310.py 中,先定义 Image 模型:
class Image(BaseModel):
url: str
name: str
4.2 把子模型用作属性类型
然后把它作为 Item 中某个属性的类型:
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
这样 FastAPI 就期望接收类似如下的请求体:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
仅凭这一处类型声明,FastAPI 就免费提供:
- 编辑器支持(代码补全等),即便针对嵌套模型内部字段;
- 数据转换(type coercion,例如把数字字符串转为数值);
- 数据校验(字段缺失、类型不符返回 422);
- 自动文档(OpenAPI 中自动生成
Image组件并引用)。
对应测试 test_tutorial004.py 验证了嵌套 image 字段的解析结果,以及缺失 url 等字段时返回的校验错误结构。
五、特殊类型与校验:HttpUrl 的实战应用
除了 str、int、float 等基础类型,还可以使用继承自 str 的更复杂的简单类型。要了解全部可选项,可查阅 Pydantic 官方的类型概览(tutorial005_py310.py 中导入的 HttpUrl 即属此类)。
例如 Image 模型中有一个 url 字段,我们可声明它必须是 Pydantic 的 HttpUrl 实例,而不是普通的 str:
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
(完整代码见 tutorial005_py310.py。)
效果有二:
- 校验:Pydantic 会测试该字符串是否为合法 URL,非法 URL(如
http://或空串)会直接返回 422 校验错误,测试 test_tutorial005.py 覆盖了这一场景; - 文档:该字段会以 URL 格式(
format: uri等)记录在 JSON Schema / OpenAPI 中,Swagger UI 会给出更精确的提示。
六、属性类型为“子模型列表”:list[Image]
Pydantic 模型还可以作为 list、set 等容器类型的内部类型使用。在 tutorial006_py310.py 中,Item 的 images 字段被声明为:
images: list[Image] | None = None
此时 FastAPI 期望(并完成转换、校验、文档化)的 JSON 请求体形如:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": [
"rock",
"metal",
"bar"
],
"images": [
{
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
},
{
"url": "http://example.com/dave.jpg",
"name": "The Baz"
}
]
}
注意 images 键现在是一个图像对象数组,列表中每个元素都会被逐一按 Image 模型校验。测试 test_tutorial006.py 验证了多张图片的解析及单张图片字段缺失时的错误响应。
七、深度嵌套模型:Offer → list[Item] → list[Image]
嵌套深度可以任意增加。tutorial007_py310.py 展示了三层嵌套结构:
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None
class Offer(BaseModel):
name: str
description: str | None = None
price: float
items: list[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
注意整个结构链:Offer 持有一个 Item 列表,而每个 Item 又可选地持有 Image 列表。OpenAPI 文档中这三个模型都会生成独立的 schema 组件并相互 $ref 引用。测试 test_tutorial007.py 会发送包含多个商品、每个商品带多张图片的完整深度嵌套请求体,并校验回显结果。
八、纯列表请求体:顶层就是 JSON 数组
通常请求体顶层是一个 JSON 对象,但如果你期望的最外层值就是一个 JSON array(Python 的 list),同样可以直接在函数参数上声明类型,语法与 Pydantic 模型字段完全一致:
images: list[Image]
如 tutorial008_py310.py 所示:
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
return images
客户端此时发送的不是对象,而是数组本身:
[
{"url": "http://example.com/baz.jpg", "name": "The Foo live"},
{"url": "http://example.com/dave.jpg", "name": "The Baz"}
]
从源码结构看,FastAPI 在 fastapi/dependencies/ 模块中通过分析函数参数注解来判断该参数是路径参数、查询参数还是请求体——当注解是 Pydantic 模型或序列容器(list 等)时,整个参数会被视为 body 来源。测试 test_tutorial008.py 验证了纯数组请求体的正常解析,以及发送非数组数据时的校验失败行为。
九、编辑器支持无处不在
由于每一层都是强类型声明,你在编辑器中能获得完整的自动补全支持,包括列表内部的元素:
![FastAPI 嵌套模型在编辑器中的自动补全效果,展示 list[Image] 字段内部元素的代码提示](https://raw.gitcode.com/GitHub_Trending/fa/fastapi/files/master/docs/en/docs/img/tutorial/body-nested-models/image01.png)
(图片源自官方教程:对 list[Image] 类型字段的编辑器代码补全。)
如果直接操作 dict 是拿不到这种编辑器支持的。但你也无需为此操心:传入的 dict 会被自动转换(解析)为模型实例,输出时也会自动序列化为 JSON。
十、任意 dict 请求体:键值各自强类型
有时你事先并不知道合法的字段名(这正是 Pydantic 模型所要求的预先声明),却仍然希望接收任意键。这时可以直接把请求体声明为 dict,并分别为键和值指定类型。
另一个典型场景是需要非字符串类型的键,比如 int 键。在 tutorial009_py310.py 中:
from fastapi import FastAPI
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
return weights
这表示:只要传入的是 int 键、float 值的任意 dict 即可接受。例如发送:
{
"index1": 10.5,
"index2": 20.25
}
这里有一个重要的细节:JSON 规范只允许字符串作为键。但 Pydantic 具备自动数据转换能力——API 客户端实际只能发送字符串键,Pydantic 会自动把它们转换并校验为 int(前提是字符串内容为纯整数),最终端点函数里拿到的 weights 确实具有 int 键和 float 值。测试 test_tutorial009.py 验证了“字符串数字键被成功转换”和“非数字键返回 422”两种情况。
十一、各示例的输入能力速查表
| 示例文件 | 关键字段声明 | 接受的请求体形态 | 关键行为 |
|---|---|---|---|
| tutorial001_py310.py | tags: list = [] |
任意元素列表 | 元素类型不校验 |
| tutorial002_py310.py | tags: list[str] = [] |
字符串数组 | 非字符串元素返回 422 |
| tutorial003_py310.py | tags: set[str] = set() |
字符串数组(可重复) | 入站去重,文档标注 uniqueItems |
| tutorial004_py310.py | image: Image | None |
内嵌 image 对象 |
按 Image 模型深度校验 |
| tutorial005_py310.py | url: HttpUrl |
url 必须为合法 URL |
非法 URL 返回 422,文档标注 uri 格式 |
| tutorial006_py310.py | images: list[Image] | None |
对象数组 | 逐元素校验 |
| tutorial007_py310.py | items: list[Item] |
三层嵌套对象 | Offer→Item→Image 全链路校验 |
| tutorial008_py310.py | 参数 images: list[Image] |
顶层即为 JSON 数组 | 每个元素按 Image 校验 |
| tutorial009_py310.py | 参数 weights: dict[int, float] |
任意键值对 | JSON 字符串键自动转换为 int 键 |
总结
在 FastAPI 中,你获得了 Pydantic 模型的全部表达能力——任意深度的嵌套、容器类型参数化、特殊校验类型、强类型 dict 键——而代码本身保持简洁、短小、优雅。并且你始终享有这些能力:
- 编辑器支持:代码补全无处不在,深入嵌套结构内部;
- 数据转换:又称解析(parsing)/序列化(serialization),如字符串数字键转
int; - 数据校验:结构不符、类型不符自动返回 422 及详细错误位置;
- Schema 文档:每个嵌套模型自动生成为 OpenAPI 组件;
- 自动文档:Swagger UI 中可直接试错嵌套结构,并得到逐字段的错误提示。
所有上述行为均有对应的自动化测试(tests/test_tutorial/test_body_nested_models/)持续验证,示例代码可复制自 docs_src/body_nested_models/ 直接在本地项目中运行。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00