首页
/ FastAPI JSON Compatible Encoder 全解析:深入理解与应用 `jsonable_encoder()`

FastAPI JSON Compatible Encoder 全解析:深入理解与应用 `jsonable_encoder()`

2026-09-06 18:25:31作者:曹令琨Iris

jsonable_encoder() 是 FastAPI 提供的一个核心序列化工具,负责把 Pydantic 模型、datetimeDecimalEnum 等各类 Python 对象,递归转换为与 JSON 完全兼容的 Python 原生数据结构(dictliststr 等)。本篇指南以官方教程《JSON Compatible Encoder》为骨架,结合 fastapi/encoders.py 的源码实现与 tests/test_jsonable_encoder.py 的测试用例,讲透它的使用场景、参数含义、支持的数据类型与内部递归机制。读完你不仅能熟练用它把对象安全地写入只接受 JSON 的数据库,还能理解 FastAPI 内部是如何借它完成响应序列化的。

为什么需要 jsonable_encoder

Web 应用中经常遇到一类场景:需要把业务对象持久化到数据库,而某些数据库(或消息队列、缓存系统)只接受 JSON 兼容的数据。但你的业务对象往往并不 JSON 兼容:

  • Pydantic 模型是一个带属性的对象,不是 dict,无法直接 json.dumps()
  • datetime 对象在 JSON 中并没有原生表达,需要转成字符串(例如 ISO 8601 格式);
  • 更复杂的类型,如 UUIDDecimalEnumPathbytesset 等,也各有各的序列化需求。

Python 标准库 json.dumps() 面对这些类型会直接抛出 TypeError,无法胜任。FastAPI 因此提供了 jsonable_encoder() 函数:它接收任意对象,返回一个所有层级值都 JSON 兼容的 Python 数据结构。正如教程所指出的,典型用途就是"存入数据库之前先做一次转换"。

核心实战:把 Pydantic 模型存入只接受 JSON 的数据库

官方教程以一个只接受 JSON 兼容数据的 fake_db 为例演示了最经典的用法。完整示例见 docs_src/encoder/tutorial001_py310.py

from datetime import datetime

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

fake_db = {}


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)
    fake_db[id] = json_compatible_item_data

这个例子要重点说明两个关键转化,它们也是 jsonable_encoder 的核心能力:

  1. Pydantic 模型 → dictItem 实例是带属性访问的对象,jsonable_encoder(item) 会先把它展开成普通字典;
  2. datetimestrtimestamp 字段是 datetime 对象,转换后成为 ISO 格式字符串(例如 "2026-09-06T18:24:46"),这正是数据库中可直接存储、日后可直接解析的形式。

需要特别澄清的一点(教程中有专门说明):jsonable_encoder() 并不返回一个大的 JSON 字符串。它返回的是 Python 标准数据结构——一个值、子值都 JSON 兼容的 dict。因此调用它的结果可以直接交给标准库 json.dumps() 编码成字符串,也可以直接存入只认 JSON 兼容结构的数据库。

参数详解:从源码签名看全部可配置项

从源码看,jsonable_encoder 的定义 签名如下:

def jsonable_encoder(
    obj: Any,
    include: IncEx | None = None,
    exclude: IncEx | None = None,
    by_alias: bool = True,
    exclude_unset: bool = False,
    exclude_defaults: bool = False,
    exclude_none: bool = False,
    custom_encoder: dict[Any, Callable[[Any], Any]] | None = None,
    sqlalchemy_safe: bool = True,
) -> Any:

各参数含义如下表所示,它们大多会被透传给 Pydantic 模型的 model_dump()

参数 默认值 作用
obj (必填) 待转换的任意输入对象
include None 仅输出这些字段(Pydantic 语义),传入非集合可自动转 set
exclude None 排除这些字段(Pydantic 语义)
by_alias True 输出是否使用字段别名(Field(alias=...))。因为 API 里设别名就是为了让结果用别名,所以默认 True
exclude_unset False 排除「未被显式赋值、仅使用了默认值」的字段
exclude_defaults False 排除「值与默认值相同」的字段(即使被显式赋值)
exclude_none False 排除值为 None 的字段
custom_encoder None 自定义类型到编码函数的映射,优先级高于内置编码规则
sqlalchemy_safe True 排除键名以 _sa 开头的字段(这是为兼容 SQLAlchemy 对象做的 hack,它们把内部状态存于 _sa 前缀属性,不应参与序列化)

其中 include/exclude 在传入非 set/dict 类型(比如列表)时,源码会先执行 set(...) 归一化,见 encoders.py 的归一化逻辑,所以传 ["name"] 这种列表写法也完全可行。

内置支持的类型:一张编码映射表

实现通过一张模块级常量表 ENCODERS_BY_TYPE 声明了大量常见类型的"快速编码器",这是"即插即用"背后的机制:

输入类型 编码方式 说明
bytes o.decode() 解码为字符串
datetime.date / datetime.datetime / datetime.time isoformat() 转 ISO 8601 字符串(教程核心案例)
datetime.timedelta td.total_seconds() 转为总秒数
Decimal decimal_encoder 无小数部分转 int,否则转 floatNaN/Infinity 保持语义(对应 float 的 nan/inf
Enum o.value 取枚举成员的值
set / frozenset / deque / 生成器 list 转成列表
UUID str 转字符串
Pathpathlib str 转字符串路径
IPv4/IPv6 Address/Interface/Network str 网络类型转字符串
NameEmail / Url / AnyUrl str Pydantic 网络类型
SecretStr / SecretBytes str 密文类型转字符串
Pattern(正则) o.pattern 取出正则源码字符串
Color / PyExtraColor(新旧 Pydantic 颜色类型) str 颜色转字符串

注意 datetime.timedeltaDecimal 的处理并非简单转字符串,体现了"JSON 兼容"而非"一律字符串化"的设计取向。测试文件 tests/test_jsonable_encoder.py#L294-L313 专门验证了 Decimalint/float 以及 NaN/Infinity 的行为。

源码级原理:递归转换的分支机制

jsonable_encoder 不是一个简单查表函数,而是一个对结构层层递归的处理器,对不同类型的对象走不同分支。梳理 encoders.py 的完整实现,主流程如下:

  1. custom_encoder 优先:若传入了自定义编码器,先按 type(obj) 精确匹配、再按 isinstance 子类匹配,命中的立即返回定制结果;
  2. Pydantic v2 模型:调用 obj.model_dump(mode="json", ...) 一次性完成字段筛选与序列化,再对结果递归执行 jsonable_encoder
  3. dataclass:先 dataclasses.asdict(obj) 展开成字典再递归,因此 dataclass 里的嵌套模型同样会被处理;
  4. Enum:返回 obj.value
  5. PurePath:转 str
  6. JSON 基本类型str/int/float/None):原样返回;
  7. dict:逐个键值递归编码,同时执行 include/exclude 键集合过滤、_sa 前缀剔除与 exclude_none 过滤,字典键也会被递归编码(见 encoders.py#L298-L306),所以非字符串键同样可以正确处理;
  8. 序列类list/set/frozenset/GeneratorType/tuple/deque):逐元素递归,统一产出列表;
  9. ENCODERS_BY_TYPE:先精确匹配 type(obj),再通过预生成的按编码器聚类的类元组做 isinstance 匹配(覆盖子类场景,见 generate_encoders_by_class_tuples);
  10. 兜底:尝试 dict(obj)vars(obj) 把普通对象展开成字典再递归;两种都失败时抛出带原始异常列表的 ValueError

值得注意的是 Pydantic v1 模型实例会在第 10 步之前被拦截并抛出 PydanticV1NotSupportedError(见 encoders.py#L341-L345),测试用例 test_json_encoder_error_with_pydanticv1 也专门验证了这一行为。

深入使用:include / exclude 与字段筛选

实际开发中经常需要"只序列化部分字段",includeexclude 在任意嵌套层级都生效。下面结合测试用例演示:

class ModelWithDefault(BaseModel):
    foo: str
    bar: str = "bar"
    bla: str = "bla"

model = ModelWithDefault(foo="foo", bar="bar")

jsonable_encoder(model)                          # {"foo": "foo", "bar": "bar", "bla": "bla"}
jsonable_encoder(model, exclude_unset=True)      # {"foo": "foo", "bar": "bar"}
jsonable_encoder(model, exclude_defaults=True)   # {"foo": "foo"}
jsonable_encoder(model, include={"foo"})         # {"foo": "foo"}
jsonable_encoder(model, exclude={"bla"})         # {"foo": "foo", "bar": "bar"}

这些断言来自 tests/test_jsonable_encoder.py#L187-L202。一个实用推论是:当你想"只把用户显式提交、而非默认值的字段写入更新记录"时,组合 exclude_unset=Trueexclude_defaults=True 即可得到最小化的差异字典。此外,字段筛选能力同样作用于 dict 输入、dataclass 以及列表/字典的任意嵌套容器——测试中 [model]{"key": model}{"key": [model]} 结构均能正确继承筛选参数(见 test_encode_model_with_default_in_dict_and_list)。

高级用法:用 custom_encoder 覆盖内置序列化

当你对内置规则不满意时,可以通过 custom_encoder 传入 {类型: 编码函数} 映射。它优先于内置编码被查找。看测试中的例子(tests/test_jsonable_encoder.py#L219-L239):

class safe_datetime(datetime):
    pass

instance = {"dt_field": safe_datetime.now()}

# 对子类 safe_datetime 单独定制,输出 "18:24:46" 这类时间字符串
jsonable_encoder(
    instance,
    custom_encoder={safe_datetime: lambda o: o.strftime("%H:%M:%S")},
)

# 对基类 datetime 定制,同样能命中子类实例
jsonable_encoder(
    instance,
    custom_encoder={datetime: lambda o: o.strftime("%H:%M:%S")},
)

从源码逻辑(先 type(obj) in custom_encoder,再遍历用 isinstance 匹配)可知,映射中声明的基类编码器也能覆盖到其子类实例,这为全局调整某一类对象的序列化方式提供了便利。同一测试文件还验证了自定义 Enum 编码器({MyEnum: custom_enum_encoder})同样生效,见 test_custom_enum_encoders

FastAPI 内部也在用它:从路由到异常处理

教程末尾的 note 强调:jsonable_encoder 实际上是 FastAPI 内部用于转换数据的工具,只是它在很多其他场景中同样好用。这条声明可以从仓库源码得到印证——它是框架序列化链路的基石:

  • 响应序列化:在 fastapi/routing.py 的 serialize_response 中,当响应没有 response_model 时,返回内容会直接走 jsonable_encoder(response_content),确保任何端点返回值都能被下游 json.dumps 处理;
  • 异常处理fastapi/exception_handlers.py#L25 使用 jsonable_encoder(exc.errors()) 把校验错误的 Error 列表转成 JSON 兼容结构后写入 HTTP 响应体 detail
  • OpenAPI 生成fastapi/openapi/utils.py#L679jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) 生成完整 OpenAPI 文档,此外 security 定义、参数的 example/examples 等也都会先经过它处理(见 fastapi/openapi/utils.py);
  • SSE 数据载荷:在 fastapi/routing.py#L536 中,Server-Sent Events 的非 Pydantic 数据载荷同样由 json.dumps(jsonable_encoder(item.data)) 序列化。

由此可见,理解 jsonable_encoder 也相当于从序列化视角理解了 FastAPI"端点返回任意对象 → 输出 JSON 响应"这条内部主路径。

总结

jsonable_encoder() 是 FastAPI 中"通用对象 → JSON 兼容结构"的标准化解决方案:

  • 用途:把 Pydantic 模型、dataclass、datetimeUUIDDecimalEnumset 等对象递归转成可用 json.dumps() 直接编码、或可直接存入只支持 JSON 数据库的 Python 原生结构;
  • 位置:定义于 fastapi/encoders.py,随 fastapi 一并安装,可从 fastapi.encoders 导入;
  • 能力边界:返回 dict 而非 JSON 字符串;内置类型映射表覆盖 20 余种常见类型;支持 include/exclude/by_alias/exclude_unset/exclude_defaults/exclude_none/custom_encoder/sqlalchemy_safe 全套精细控制;
  • 纵深价值:它既是教程推荐给开发者的数据库写入工具,也是 FastAPI 在响应序列化、异常处理与 OpenAPI 文档生成等环节的内部依赖,理解它有助于把握整个框架的序列化设计。

若要查看配套的可运行示例与行为契约,可分别参考 docs_src/encoder/tutorial001_py310.pytests/test_jsonable_encoder.py

登录后查看全文
热门项目推荐
相关项目推荐