FastAPI 直接返回 Response:JSONResponse、jsonable_encoder 与自定义响应实践指南
本文围绕 FastAPI 官方文档《レスポンスを直接返す》(docs/ja/docs/advanced/response-directly.md)展开,讲解如何在 path operation 中直接返回 Response 对象:何时应该直接构造 JSONResponse、如何用 jsonable_encoder 把 Pydantic 模型等数据转换为 JSON 兼容内容、以及如何返回 XML 等完全自定义的响应。读完本文,你将理解 FastAPI 对「返回普通数据」与「返回 Response」两条链路的处理差异,并能结合 fastapi/routing.py 的源码判断自己项目应该走哪条路径。
FastAPI 的默认响应处理方式
创建 FastAPI 的 path operation 时,通常可以返回任意数据:dict、list、Pydantic 模型、数据库模型等。默认情况下,FastAPI 会按以下规则处理返回值:
- 声明了 Response Model(
response_model参数或返回类型注解)时,FastAPI 使用 Pydantic 将数据序列化为 JSON; - 未声明 Response Model 时,FastAPI 使用 JSON 兼容编码器 中介绍的
jsonable_encoder把数据转换后,放入JSONResponse返回; - 当然,你也可以直接构造一个
JSONResponse并返回。
性能提示:通常使用 Response Model 比直接返回
JSONResponse性能要好得多,因为声明 Response Model 后数据由 Pydantic 在 Rust 层完成序列化。
源码视角:返回普通数据时的处理路径
从源码结构看,上述「未声明 Response Model 时走 jsonable_encoder + JSONResponse」的路径可以在 fastapi/routing.py 中得到印证:当 path operation 的返回值不是 Response 实例时,FastAPI 会调用 serialize_response(内部走 jsonable_encoder)完成转换,再用实际注册的 response class(默认为 JSONResponse)包装内容;而当 response_field 存在(即声明了返回类型或 response model)且未设置自定义 response class 时,源码会启用 use_dump_json 快速路径——由 Pydantic 的 Rust 核心直接生成 JSON 字节流,跳过「中间 Python dict + json.dumps()」这一步,最终直接构造一个 media_type="application/json" 的 Response 返回(见 fastapi/routing.py 处的注释与实现)。
直接返回 Response
你可以返回 Response 或它的任何子类。
注意:JSONResponse 本身就是 Response 的子类。
当返回值是 Response 实例时,FastAPI 会直接把它透传出去:
- 不会用 Pydantic 模型做任何数据转换;
- 不会把内容转换为任何类型;
- 不会执行 Response Model 声明的校验与过滤。
这一点在 fastapi/routing.py 中体现得非常直接:
if isinstance(raw_response, Response):
if raw_response.background is None:
raw_response.background = solved_result.background_tasks
response = raw_response
也就是说,返回值是 Response 时唯一的"干预",只是在响应没有设置 background 时补上依赖中解析出的后台任务。
这种透传带来两方面的影响:
- 灵活性:可以返回任意数据格式、覆盖任何数据声明或校验;
- 责任:返回的数据是否正确、格式是否正确、能否被序列化,都由你自己保证。
在 Response 中使用 jsonable_encoder
由于 FastAPI 不会对你返回的 Response 做任何修改,你必须确保其内容已经是"可发送"的状态。
例如,不能把一个 Pydantic 模型直接塞进 JSONResponse——必须先把它转换成 dict,且其中所有数据类型(如 datetime、UUID 等)都必须是 JSON 兼容类型。此时可以在把数据交给响应之前,用 jsonable_encoder 做转换:
from datetime import datetime
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from pydantic import BaseModel
class Item(BaseModel):
title: str
timestamp: datetime
description: str | None = None
app = FastAPI()
@app.put("/items/{id}")
def update_item(id: str, item: Item):
json_compatible_item_data = jsonable_encoder(item)
return JSONResponse(content=json_compatible_item_data)
完整示例见 docs_src/response_directly/tutorial001_py310.py。
jsonable_encoder 能转换哪些类型
jsonable_encoder 定义于 fastapi/encoders.py,支持 include / exclude / by_alias / exclude_unset / exclude_defaults / exclude_none / custom_encoder 等参数,与 Pydantic 的字段过滤语义一致。从 ENCODERS_BY_TYPE 映射表 可以看到,它内置了针对 UUID: str、SecretStr: str、SecretBytes: str、Path: str、AnyUrl: str、set: list 等类型的转换规则;datetime 则通过 str 转换。此外它也能处理 Pydantic 模型实例(转为 dict)、dataclass、任意对象(调用 __dict__)等。
技术细节:fastapi.responses 与 starlette.responses
你同样可以 from starlette.responses import JSONResponse。
FastAPI 只是出于开发者便利,把 starlette.responses 中的同名内容以 fastapi.responses 的形式再提供了一遍。可用的大多数响应类都直接来自 Starlette。从 fastapi/responses.py 可以看到,FileResponse、HTMLResponse、JSONResponse、PlainTextResponse、RedirectResponse、Response、StreamingResponse 等全部是从 starlette.responses 直接 re-export 的;FastAPI 自身仅额外提供了 SSE 的 EventSourceResponse,以及已标记弃用的 UJSONResponse / ORJSONResponse(源码注释说明:当设置了返回类型或 response model 时,FastAPI 已能通过 Pydantic 直接序列化到 JSON 字节,更快且无需自定义响应类)。
返回自定义 Response
上面的示例展示了全部所需部件,但实用性有限——你完全可以直接返回 item,让 FastAPI 默认地帮你转成 dict 并包进 JSONResponse。直接返回 Response 真正的价值在于返回自定义格式的响应。
比如你想返回一个 XML 响应。可以把 XML 内容放进字符串,塞进 Response,设置对应的 media_type,然后返回:
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/legacy/")
def get_legacy_data():
data = """<?xml version="1.0"?>
<shampoo>
<Header>
Apply shampoo here.
</Header>
<Body>
You'll have to use soap here.
</Body>
</shampoo>
"""
return Response(content=data, media_type="application/xml")
完整示例见 docs_src/response_directly/tutorial002_py310.py。由于该返回值是 Response 实例,FastAPI 不会触碰 data 的内容,XML 会按原样发出,Content-Type 头为 application/xml。同样的思路可以扩展到 PDF、CSV、自定义二进制协议等场景。
Response Model 的工作机制
当你在 path operation 中声明 Response Model(返回类型) 时,FastAPI 会用 Pydantic 通过它把数据序列化为 JSON:
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[str] = []
@app.post("/items/")
async def create_item(item: Item) -> Item:
return item
@app.get("/items/")
async def read_items() -> list[Item]:
return [
Item(name="Portal Gun", price=42.0),
Item(name="Plumbus", price=32.0),
]
完整示例见 docs_src/response_model/tutorial001_01_py310.py。
因为序列化发生在 Rust 侧,性能远好于用常规 Python 加 JSONResponse 类的做法。使用 response_model 或返回类型时,FastAPI 不会使用 jsonable_encoder 转换数据(那会更慢),也不会使用 JSONResponse 类。取而代之的是:它直接取 Pydantic 用 response model(或返回类型)生成的 JSON 字节,返回一个携带正确 JSON 媒体类型(application/json)的 Response。这与 fastapi/routing.py 中的 use_dump_json 快速路径完全对应。
备注:直接返回 Response 的边界
直接返回 Response 时:
- 数据不会被校验;
- 不会被转换(序列化);
- 不会被自动写入 OpenAPI 文档。
不过你仍然可以按照 OpenAPI 中的 Additional Responses 所描述的方式,手动为这些响应编写文档。后续章节(Custom Response、Response Class 等)会介绍如何在继续自动数据转换与文档生成的同时使用/声明这些自定义 Response。
小结:如何选型
| 场景 | 推荐做法 | 原因 |
|---|---|---|
| 常规 JSON API | 声明 Response Model 或返回类型 | Pydantic Rust 层序列化,性能最好,且自动校验、过滤、生成文档 |
需要控制 JSON 内容(如 exclude、自定义头)但不想用 response model |
jsonable_encoder + JSONResponse |
FastAPI 不干预,你自己掌控输出 |
| 返回 XML / PDF / 其他非 JSON 格式 | Response(content=..., media_type=...) 或对应子类 |
直接透传,任意 media_type |
| 性能敏感的高频 JSON 端点 | 优先 Response Model | 避免 jsonable_encoder 的 Python 侧开销 |
核心原则一句话概括:FastAPI 只透传 Response,其余都帮你做;透传的自由度换来了序列化、校验与文档的全部责任。
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 StartedRust0627
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