首页
/ FastAPI 实战:用 Base64 在 JSON 请求与响应中传输字节数据(val_json_bytes 与 ser_json_bytes)

FastAPI 实战:用 Base64 在 JSON 请求与响应中传输字节数据(val_json_bytes 与 ser_json_bytes)

2026-09-06 14:29:08作者:瞿蔚英Wynne

JSON 协议本身只能承载 UTF-8 字符串,无法直接存放二进制内容。当你的 FastAPI 应用必须在 JSON 请求体或响应体中携带二进制数据(例如把一段图片、证书或文件字节嵌入结构化 JSON)时,标准的做法是对字节做 Base64 编码。本文基于 FastAPI 官方文档 JSON with Bytes as Base64 及其配套示例 docs_src/json_base64_bytes/tutorial001_py310.py,完整讲解如何用 Pydantic 的 val_json_bytesser_json_bytes 模型配置,让 FastAPI 自动完成 Base64 的解码入参与编码出参,并深入到源码层面说明 OpenAPI 文档是如何声明该字段的。

FastAPI /docs 界面中 DataInput 模型声明 data 字段期望 base64 编码的字节数据

Base64 与文件上传的取舍:先想清楚是否真的需要 JSON 内嵌二进制

官方文档在正文一开始就给出了一条重要建议:优先考虑是否可以用文件上传/文件响应代替 Base64

原因很直接:

  1. JSON 只能包含 UTF-8 编码的字符串,原始字节(bytes)无法直接放入 JSON 文档;
  2. Base64 编码必然膨胀体积。它用 6 个比特编码 8 个比特,通常比原始二进制数据多出约 1/3 的字符量,因此一般情况下,Base64 内嵌 JSON 的传输效率低于直接传文件(multipart/form-data + 二进制流);
  3. 只有在确实必须把二进制数据放进 JSON、且无法改用文件的场景(例如与某个只接受纯 JSON 的第三方系统对接、数据中需要同时携带结构化字段与二进制字段)时,才建议使用 Base64。

输入方向:用 val_json_bytes 让 Pydantic 自动解码 Base64

在 Pydantic 模型中声明 bytes 类型字段后,通过 model_config 设置 val_json_bytes: "base64",Pydantic 在校验(validate)JSON 输入时会把该字段的 Base64 字符串解码回原始字节:

from fastapi import FastAPI
from pydantic import BaseModel


class DataInput(BaseModel):
    description: str
    data: bytes

    model_config = {"val_json_bytes": "base64"}


app = FastAPI()


@app.post("/data")
def post_data(body: DataInput):
    content = body.data.decode("utf-8")
    return {"description": body.description, "content": content}

以上代码来自完整示例 docs_src/json_base64_bytes/tutorial001_py310.pyDataInput 模型与 /data 的 POST 端点。

请求与响应示例

启动应用后访问 /docs,可以看到 data 字段被声明为期望 base64 编码的字节(见文首截图)。你可以发送如下请求:

{
    "description": "Some data",
    "data": "aGVsbG8="
}

aGVsbG8= 是字符串 hello 的 Base64 编码。

Pydantic 会自动把该 Base64 字符串解码为字节,端点内拿到的是原始 bytes 对象 body.data;上例再 decode("utf-8") 得到文本,返回响应:

{
  "description": "Some data",
  "content": "hello"
}

对应的自动化测试在 tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py 中:POST /data 发送 data: "SGVsbG8sIFdvcmxkIQ=="(即 Hello, World! 的 Base64),断言响应为 {"description": "A file", "content": "Hello, World!"},验证了解码链路完整可用。

输出方向:用 ser_json_bytes 让 Pydantic 自动编码字节为 Base64

响应方向用另一个配置项 ser_json_bytes:当模型在序列化(serialize)JSON 响应时,Pydantic 会把 bytes 字段编码为 Base64 字符串输出:

class DataOutput(BaseModel):
    description: str
    data: bytes

    model_config = {"ser_json_bytes": "base64"}


@app.get("/data")
def get_data() -> DataOutput:
    data = "hello".encode("utf-8")
    return DataOutput(description="A plumbus", data=data)

该代码取自 tutorial001_py310.py。注意端点返回的是字节对象 data,而客户端实际收到的 JSON 是:

{
  "description": "A plumbus",
  "data": "aGVsbG8="
}

测试同样覆盖了这一行为(test_get_data):GET /data 的响应体中 data 字段断言为 Base64 字符串 "aGVsbG8="

输入与输出双向:同一个模型同时处理请求和响应

如果一个端点既接收又返回带字节的数据,可以直接用同一个模型同时配置两个选项:

class DataInputOutput(BaseModel):
    description: str
    data: bytes

    model_config = {
        "val_json_bytes": "base64",
        "ser_json_bytes": "base64",
    }


@app.post("/data-in-out")
def post_data_in_out(body: DataInputOutput) -> DataInputOutput:
    return body

取自 tutorial001_py310.py。请求时 val_json_bytes 负责把 Base64 解码为 bytes,响应时 ser_json_bytes 再把 bytes 编码回 Base64,两端自动对称。测试 test_post_data_in_out 验证了发送 {"data": "SGVsbG8sIFdvcmxkIQ=="} 后原样收到 Base64 字符串。

源码级原理:OpenAPI 文档如何声明 Base64 字段

上面三节代码之所以在 /docs 中自动显示为 "expects base64 encoded bytes",是因为 FastAPI 在生成 JSON Schema 时对 bytes 字段做了专门处理。在 fastapi/_compat/v2.py 中,FastAPI 定义了 GenerateJsonSchema.bytes_schema 方法:

def bytes_schema(self, schema: CoreSchema) -> JsonSchemaValue:
    json_schema = {"type": "string", "contentMediaType": "application/octet-stream"}
    bytes_mode = (
        self._config.ser_json_bytes
        if self.mode == "serialization"
        else self._config.val_json_bytes
    )
    if bytes_mode == "base64":
        json_schema["contentEncoding"] = "base64"
    self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes)
    return json_schema

从这段源码可以看到几个关键实现事实:

  • bytes 字段在 JSON Schema 中生成 type: "string",并附带 contentMediaType: "application/octet-stream"
  • FastAPI 会区分校验模式序列化模式:生成请求体(validation)schema 时读取 val_json_bytes,生成响应体(serialization)schema 时读取 ser_json_bytes
  • 只有对应模式的配置值为 "base64" 时,才会在 schema 上追加 contentEncoding: "base64" 标注。

这一点也被 OpenAPI 测试用例完整印证:test_openapi_schema 断言了 DataInputDataOutputDataInputOutput 三个模型的 data 字段 schema 均为:

{
    "type": "string",
    "contentEncoding": "base64",
    "contentMediaType": "application/octet-stream",
    "title": "Data"
}

也就是说,无论前端代码生成器、API 客户端还是人工查阅,都能从 OpenAPI 规范中明确得知该字段需要 Base64 编码,而不需要额外的文字说明。

使用建议与限制小结

结合文档与源码,可以归纳出如下实践要点:

  1. 默认不用 Base64:能上传/下载文件就用 Request FilesFileResponse,Base64 仅用于"必须内嵌 JSON"的场景;
  2. 入参与出参是两个独立开关val_json_bytes 只影响 JSON 输入校验,ser_json_bytes 只影响 JSON 输出序列化,可按需单独或同时启用(完整可运行示例见 docs_src/json_base64_bytes/tutorial001_py310.py);
  3. 端点内拿到/给出的是 bytes:解码与编码全部由 Pydantic 在 JSON 边界上完成,业务代码始终操作原生 bytes 对象,无需手工 base64.b64decode/b64encode
  4. 版本前提val_json_bytes / ser_json_bytes 是 Pydantic v2 的模型配置项,当前仓库 pyproject.toml 中声明依赖 pydantic>=2.9.0,因此本方案要求 Pydantic v2 环境;
  5. 文档自动化:配置生效后,contentEncoding: base64 会自动出现在 /openapi.json 中(见 fastapi/_compat/v2.pybytes_schema 实现),API 文档无需手工维护。
登录后查看全文
热门项目推荐
相关项目推荐