首页
/ FastAPI 在 JSON 中传输二进制 Bytes:基于 Pydantic v2 的 Base64 编码实战(`val_json_bytes` 与 `ser_json_bytes`)

FastAPI 在 JSON 中传输二进制 Bytes:基于 Pydantic v2 的 Base64 编码实战(`val_json_bytes` 与 `ser_json_bytes`)

2026-09-07 17:52:40作者:盛欣凯Ernestine

许多业务场景需要在 JSON 请求与响应中携带图片、文件内容、哈希摘要等二进制数据,但 JSON 本质上只允许 UTF-8 字符串。本文围绕 FastAPI 官方文档「JSON with Bytes as Base64」展开,讲解如何借助 Pydantic v2 的模型配置项 val_json_bytes / ser_json_bytes,让 FastAPI 在接收 JSON 时自动把 Base64 字符串解码为 bytes、在返回 JSON 时自动把 bytes 编码为 Base64 字符串。读完你可以在不接触原始字节的前提下,直接用类型注解完成「JSON ↔ 二进制」的双向转换,并获得规范的 OpenAPI 文档支持。

前提:JSON 里为什么需要 Base64?

FastAPI 应用经常需要「接收和发送 JSON 数据,但数据里又包含二进制内容」的场景。首先要理解一个硬性约束:

JSON 只能包含 UTF-8 编码的字符串,因此它无法承载原始字节(raw bytes)。

要想在 JSON 里携带二进制,就必须把原始字节编码成字符串,最通用的做法就是 Base64

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

上面 data 字段的 aGVsbG8= 正是字符串 hello 的 Base64 编码——先取 hello 的 UTF-8 字节序列,再做 Base64 编码即可得到。对端拿到后先 Base64 解码、再按 UTF-8 解码,就还原出了原始内容。

先考虑 Files 方案:Base64 并非首选

在引入任何 JSON 编码方案之前,官方文档建议先评估你是否真的需要在 JSON 内嵌二进制,而非走「文件」通道:

原因是 Base64 编码会带来明显的空间膨胀:编码后的字符串字符数通常多于原始二进制数据本身,效率天然低于直接传输文件。因此文档给出的指导原则很明确:

仅当确实必须把二进制数据放进 JSON、且无法改用文件方式时,才使用 Base64。

核心机制:Pydantic v2 的 val_json_bytesser_json_bytes

本方案的关键并非 FastAPI 自身的序列化代码,而是 Pydantic v2 提供的一对模型配置项。当前仓库在 pyproject.toml 中声明依赖 pydantic>=2.9.0,<3.0.0,这两个配置项正是在 v2 模型上生效的 JSON 行为开关:

配置项 作用阶段 行为
val_json_bytes 输入校验(validation) 告诉 Pydantic:当从 JSON 解析 bytes 类型字段时,按该取值解释输入;取 "base64" 时会把 Base64 字符串解码为原始 bytes
ser_json_bytes 输出序列化(serialization) 告诉 Pydantic:当把 bytes 类型字段写入 JSON 响应时,按该取值编码;取 "base64" 时会把 bytes 编码为 Base64 字符串

两者取值均为 "base64"。声明方式是在 Pydantic 模型类中设置 model_config

model_config = {"val_json_bytes": "base64"}   # 仅输入
model_config = {"ser_json_bytes": "base64"}   # 仅输出
model_config = {"val_json_bytes": "base64", "ser_json_bytes": "base64"}  # 双向

文档配套的完整可运行示例位于 docs_src/json_base64_bytes/tutorial001_py310.py,下面按「输入 / 输出 / 双向」三种形态逐一拆解。

仅处理输入数据:解码 Base64 为 bytes

先看「只负责接收」的模型。它声明了一个 bytes 类型字段 data,并在模型配置中打开 val_json_bytes: "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}

当客户端 POST 下面这样的 JSON 请求体时:

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

Pydantic 会在校验阶段自动完成 aGVsbG8= 的 Base64 解码,把真正的原始字节塞进 body.data。路由函数里因此能直接调用 body.data.decode("utf-8") 得到明文内容,并把解码结果随响应返回:

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

打开 FastAPI 自动生成的交互式文档 /docs,可以看到请求体模型里字段 data 已被标注为期望 Base64 编码的字节串;Swagger UI 中字段类型虽然显示为 string,但示例值形如 Base64 文本。下图即为官方文档配套的 Swagger UI 截图,data 字段在文档界面中被直观展示为可填入 Base64 字符串的字节字段:

FastAPI /docs Swagger UI 中请求体 data 字段的 Base64 字节提示

更严谨的证据来自自动生成的 OpenAPI Schema。仓库测试 tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py 中对 /openapi.json 的断言显示,DataInputdata 字段在 schema 中体现为:

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

contentEncoding: base64contentMediaType: application/octet-stream 是对「二进制经 Base64 编码传输」的标准 OpenAPI 表达,客户端工具(如代码生成器)可以据此正确理解字段语义。

仅处理输出数据:把 bytes 序列化为 Base64

再看不负责接收、只负责返回的模型,它在模型配置中打开的是 ser_json_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)

路由内部仍然以原生 bytes 组织数据("hello".encode("utf-8")),返回类型标注为 DataOutput。当 FastAPI 生成 JSON 响应时,Pydantic 会按 ser_json_bytes 的配置把 bytes 序列化为 Base64 字符串,客户端收到的响应是:

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

也就是说,业务代码全程只接触 bytes,Base64 编码细节完全由模型层负责,路由不需要任何手动 base64 编码调用。

输入输出共用同一模型:双向 Base64

业务中最常见的其实是「同一个数据模型既要被请求体校验、又要作为响应返回」。此时只需把两个配置项同时打开:

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

这里的 POST /data-in-out 把请求体原样返回:进入时 data 从 Base64 解码为 bytes,返回时又从 bytes 编码回 Base64,JSON 线格式两侧完全一致。例如发送:

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

响应会被原样回显为 data: "SGVsbG8sIFdvcmxkIQ=="(即 Hello, World! 的 Base64 编码)。

需要留意:同一模型在文档与 OpenAPI Schema 中只会生成一份 DataInputOutput 定义(见测试中对 /openapi.json 的断言),但由于 bytes 字段同时具备 contentEncoding: base64 信息,客户端在请求与响应两侧都能正确解析。

测试用例佐证:三种端点的实际行为

仓库用 TestClient 为上述示例编写了完整测试,位于 tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py,覆盖三条路径:

  • POST /data:提交 data: "SGVsbG8sIFdvcmxkIQ==",断言响应 content 为解码后的 Hello, World!(验证输入侧 Base64 → bytes 解码);
  • GET /data:断言响应 dataaGVsbG8=(验证输出侧 bytes → Base64 编码);
  • POST /data-in-out:提交 Base64 数据后断言原样回显(验证同一模型双向转换)。

如果安装好依赖并准备就绪,可以在仓库根目录直接复现:

uv run pytest tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py

测试文件通过 needs_py310 标记按 Python 3.10+ 运行示例模块(示例源码命名为 tutorial001_py310.py,使用该写法即代表要求 Python 3.10+ 环境),同时 test_openapi_schema 用例把完整 OpenAPI 输出固化为快照,任何 schema 变化都会被测试捕获。

总结与使用建议

把二进制数据放进 FastAPI 的 JSON 请求 / 响应时,本方案的核心收益是零手工转换

  1. 模型字段直接声明为 bytes,让类型系统准确表达二进制语义;
  2. 通过 model_config 中的 val_json_bytes: "base64" 让 Pydantic 在输入校验时自动把 Base64 解码为 bytes
  3. 通过 model_config 中的 ser_json_bytes: "base64" 让 Pydantic 在输出序列化时自动把 bytes 编码为 Base64;
  4. /docs/openapi.json 会以 contentEncoding: base64contentMediaType: application/octet-stream 标准元数据标注字段,Swagger UI 自动生成可测试的请求体,代码生成工具也能正确理解字段。

最后重申文档给出的取舍建议:Base64 比原始二进制占用更多字符、传输效率更低,仅在确实无法改用 Request Files / FileResponse 传输文件的场景下使用;能用文件走文件的方案,往往更简单也更高效。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388