首页
/ FastAPI 在 JSON 中传输 Bytes:使用 Pydantic 的 `val_json_bytes` / `ser_json_bytes` 做 Base64 编解码

FastAPI 在 JSON 中传输 Bytes:使用 Pydantic 的 `val_json_bytes` / `ser_json_bytes` 做 Base64 编解码

2026-09-07 09:27:42作者:魏侃纯Zoe

导读

当你的 FastAPI 应用必须以 JSON 承载二进制数据(图片片段、加密摘要、文件指纹等)时,单纯使用 bytes 字段会导致 JSON 序列化失败——因为 JSON 只能存放 UTF-8 字符串。本指南以官方进阶教程 JSON con Bytes como Base64 为主体,讲解如何借助 Pydantic v2 模型配置中的 val_json_bytes(输入校验)与 ser_json_bytes(输出序列化),让 bytes 字段在 JSON 请求与响应中自动以 Base64 字符串传输。读完你将掌握输入、输出、双向三种场景下的完整实现方案,并理解其底层校验与 OpenAPI 文档的表现形式。

为什么需要 Base64:先想清楚能否用文件

二进制数据要进入 JSON,首先会碰到一个硬性约束:JSON 只能包含 UTF-8 编码的字符串,无法承载原始字节(raw bytes)。Base64 可以把任意二进制数据编码成 ASCII 字符串,从而把它"塞进"JSON 的字符串槽位。

但在动手之前,请先评估是否真的需要这么做。官方文档明确建议优先考虑两条更"正统"的路径:

文档强调了两个关键理由:

  1. Base64 是一种 6-bit 到 8-bit 的映射,编码后的字符数会比原始二进制数据多出约三分之一,传输与存储效率通常低于直接传文件
  2. 因此,只有在确实必须把二进制数据放进 JSON、且无法改用文件方案时,才应当使用 Base64。

完整示例代码

以下示例取自本仓库的 docs_src/json_base64_bytes/tutorial001_py310.py,它用三个 Pydantic 模型分别演示了"仅输入"、"仅输出"和"输入输出双向"三种 Base64 处理方式,并注册在三个端点上:

from fastapi import FastAPI
from pydantic import BaseModel


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

    model_config = {"val_json_bytes": "base64"}


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

    model_config = {"ser_json_bytes": "base64"}


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

    model_config = {
        "val_json_bytes": "base64",
        "ser_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}


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


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

这段代码展示了一个完整的 FastAPI 应用:启动后即可通过 POST /dataGET /dataPOST /data-in-out 三个接口验证三种配置的差异。三个模型都声明了 data: bytes 字段,唯一区别在于 model_config 中开启的选项不同。

Pydantic bytes 用于输入数据校验(val_json_bytes

如果你需要接收 JSON 请求体、并让其中的 Base64 字符串在校验阶段被解码成真实字节,就在模型配置中设置:

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

    model_config = {"val_json_bytes": "base64"}

配置了 val_json_bytes: "base64" 之后,Pydantic 在校验输入 JSON 时,会把 data 字段里的 Base64 字符串解码为原始 bytes,再按 bytes 类型继续处理。这意味着一份这样的请求可以被正确接收:

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

小提示:aGVsbG8= 正是字符串 hello 的 Base64 编码。

解码之后,FastAPI 会把原始的字节值赋给模型字段 body.data。若端点在处理时对字节做 decode("utf-8") 并返回文本,那么响应会是:

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

在 /docs 中呈现为 Base64 编码的字节

当你访问该应用的交互式 API 文档 /docs(Swagger UI)时,可以看到 data 字段被标注为期望 Base64 编码的字节数据,示例值与编辑区域直接展示可提交的 Base64 字符串:

FastAPI 自动生成的 API 文档中,POST /data 的 data 字段以 Base64 字符串形式作为请求体展示

图中所见的字节类型标注来自 FastAPI 依据 OpenAPI 规范自动生成的 schema:开启 Base64 处理后,data 字段在 schema 中会被描述为 type: string,并附带 contentEncoding: base64contentMediaType: application/octet-stream 两个声明。这一结构通过仓库中的测试被精确固化了下来——在 tests/test_tutorial/test_json_base64_bytes/test_tutorial001.pytest_openapi_schema 里,/openapi.json 的快照断言了 DataInput 模型的 data 字段正是:

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

该测试文件同时用 TestClient 验证了真实请求行为:向 /data 提交 {"description": "A file", "data": "SGVsbG8sIFdvcmxkIQ=="} 时,接口返回 {"description": "A file", "content": "Hello, World!"}——即 Base64 字符串已在服务端被还原为原始文本字节。这是上述配置端到端有效的直接证据。

Pydantic bytes 用于输出数据序列化(ser_json_bytes

反过来,如果应用内部持有真实的 bytes(例如从文件或数据库读出的二进制内容),而你想在 JSON 响应中把字节安全地发给客户端,则在模型配置中设置:

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

    model_config = {"ser_json_bytes": "base64"}

设置 ser_json_bytes: "base64" 后,Pydantic 在序列化输出数据生成 JSON 响应时,会自动把 bytes 字段编码为 Base64 字符串。示例如下:

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

注意这里 data = "hello".encode("utf-8") 是真实的字节对象,但由于响应模型声明了 ser_json_bytes,客户端收到的 JSON 中 data 会是被编码过的 Base64 字符串 "aGVsbG8="。这一点同样有测试背书:test_get_data 断言 GET /data 的响应精确等于 {"description": "A plumbus", "data": "aGVsbG8="}

同一模型同时处理输入与输出

最方便的做法,是让同一个模型既负责接收 Base64 输入、又负责生成 Base64 输出,即同时开启两个配置项:

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

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

当端点把请求体原样作为响应返回(或经过业务处理后再返回)时,双向的 Base64 编解码都会自动完成,无需手写任何编码/解码代码:

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

测试 test_post_data_in_out 证实了这一点:向 /data-in-out 提交 {"description": "A plumbus", "data": "SGVsbG8sIFdvcmxkIQ=="},接口原样返回相同 JSON——输入侧完成解码校验、输出侧完成重新编码,两个环节在请求-响应周期内无缝衔接。

底层实现机制小结

从配置到行为,可以将这套机制的要点归纳如下:

配置项 取值 作用阶段 行为
val_json_bytes "base64" 输入校验(validate) 将 JSON 请求中的 Base64 字符串解码为 bytes 赋给字段
ser_json_bytes "base64" 输出序列化(serialize) 将模型中的 bytes 编码为 Base64 字符串写入 JSON 响应
  • 两者可以独立开启,也可以同时开启;取值均为字符串 "base64"
  • 开启后,FastAPI 生成的 OpenAPI schema 会为 bytes 字段标注 type: string + contentEncoding: base64 + contentMediaType: application/octet-stream,交互式文档与客户端代码生成工具都能据此正确理解该字段(可对照 tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py 中的 schema 快照)。
  • 模型配置采用 Pydantic v2 的 model_config 字典写法,说明该能力建立在 Pydantic v2 的序列化/校验配置体系之上,本仓库使用的正是这一版本约定。

使用建议

  • 优先文件方案:凡是二进制内容可以通过 multipart 上传(Request Files)或通过 FileResponse 下发,都应避免走 JSON,以换取更小的体积与更高的传输效率。
  • 明确需要 JSON 内嵌二进制时再启用 Base64:例如需要把文件指纹、哈希、小段二进制随结构化元数据一并提交,或客户端协议约束为纯 JSON 时,才使用上文配置。
  • 保持编解码声明收敛在模型层:把 val_json_bytes / ser_json_bytes 配置在 Pydantic 模型上,业务代码拿到的始终是真正的 bytes(输入)或给出真正的 bytes(输出),编解码细节完全由模型配置接管,代码既清晰又不易出错。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

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