首页
/ FastAPI 进阶:在 JSON 中用 Base64 传输二进制数据(bytes 字段实战)

FastAPI 进阶:在 JSON 中用 Base64 传输二进制数据(bytes 字段实战)

2026-09-06 11:18:31作者:贡沫苏Truman

本篇指南讲解 FastAPI 中一个进阶但非常实用的场景:当你的接口必须接收和发送 JSON 数据、其中又需要携带二进制内容(bytes)时,如何利用 Pydantic 的 val_json_bytesser_json_bytes 配置,把二进制数据安全地以 base64 编码嵌入 JSON 请求体与响应体。读完本文,你将掌握 base64 方案与文件上传/下载方案的取舍原则、bytes 字段模型配置的完整写法,以及 FastAPI 源码中 OpenAPI Schema 是如何自动生成 contentEncoding: base64 声明的底层机制。

何时需要用 Base64 而不是文件

如果你的应用需要接收和发送 JSON 数据,但其中必须包含二进制数据,就可以把这些二进制数据编码为 base64 字符串来传输。

在选择方案之前,先评估是否可以直接使用 请求文件 来上传二进制数据、使用 自定义响应 – FileResponse 来下发二进制数据,而不是把二进制内容编码进 JSON。两者的取舍依据如下:

  • JSON 只能包含 UTF-8 编码的字符串,因此它无法承载原始字节(raw bytes);
  • Base64 可以把二进制数据编码成字符串,但代价是需要比原始二进制数据更多的字符(通常膨胀约 1/3),因此在传输效率上一般不如直接传文件;
  • 只有当你确实必须把二进制数据内嵌在 JSON 中、且无法改用文件方案时,才使用 base64

用 Pydantic bytes 字段接收输入数据

声明一个带 bytes 字段的 Pydantic 模型,并在模型配置中设置 val_json_bytes,即可告诉 Pydantic:在校验(validate)输入的 JSON 数据时使用 base64。校验过程中,base64 字符串会被自动解码为字节对象。

完整示例见 docs_src/json_base64_bytes/tutorial001_py310.py,其中接收端模型为:

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,Swagger UI 会展示字段 data 期望接收 base64 编码的字节:

Swagger UI 中 POST /data 接口,请求体示例显示 data 字段为 base64 字符串

此时可以发送如下请求:

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

提示:aGVsbG8= 就是字符串 hello 的 base64 编码。

Pydantic 会解码这个 base64 字符串,并在模型的 data 字段中把原始字节交给你。随后你会收到类似这样的响应:

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

这里的关键点在于:端点函数拿到的 body.data 已经是解码后的 bytes 类型(示例中再用 .decode("utf-8") 转回字符串),base64 编解码完全由 Pydantic 在模型边界处自动完成,业务代码无需手动调用任何 base64 模块。

用 Pydantic bytes 字段输出数据

对于输出数据,可以在模型配置中使用 ser_json_bytes。Pydantic 在生成 JSON 响应时会把字节序列化(serialize)为 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)

响应体中 data 字段就会以 "aGVsbG8=" 这样的 base64 字符串形式返回。仓库中的测试 tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py 正是如此断言的:

def test_get_data(client: TestClient):
    response = client.get("/data")
    assert response.status_code == 200, response.text
    assert response.json() == {"description": "A plumbus", "data": "aGVsbG8="}

测试同时覆盖了输入方向(发送 SGVsbG8sIFdvcmxkIQ== 解码为 Hello, World!),验证了这套机制在真实请求/响应链路中的端到端行为。

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

当然,你也可以配置同一个模型,让 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

此时请求体里的 base64 字符串会被解码成 bytes 传入端点,端点把同一个模型对象返回后,bytes 又会以 base64 形式序列化进响应。上述测试文件中的 test_post_data_in_out 验证了这一回环:发送 "SGVsbG8sIFdvcmxkIQ==",响应体中 data 原样返回同一 base64 字符串。

源码视角:OpenAPI Schema 是怎么知道要声明 base64 的

一个值得注意的细节是:配置 val_json_bytes / ser_json_bytes 后,OpenAPI 文档会自动bytes 字段生成 contentEncoding: "base64"contentMediaType: "application/octet-stream" 声明,/docs 界面因此能正确提示该字段期望 base64 字符串。从上面的测试快照(test_openapi_schema)可以看到生成的 Schema 片段:

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

这一行为来自 FastAPI 对 Pydantic JSON Schema 生成器的定制覆盖,位于 fastapi/_compat/v2.py

class GenerateJsonSchema(_GenerateJsonSchema):
    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

从这段源码可以看出两个要点:

  1. FastAPI 重写了 Pydantic 的 bytes_schema 方法,先按 bytes 的默认语义生成 type: string + contentMediaType: application/octet-stream
  2. 然后根据当前模式(校验模式读 val_json_bytes、序列化模式读 ser_json_bytes),当配置值为 "base64" 时追加 contentEncoding: base64 声明。

也就是说,Schema 声明与实际的数据编解码行为是同一份模型配置驱动的两面:同一组 model_config 既决定运行时如何解码/编码字节,也决定 OpenAPI 文档如何向调用方描述字段格式。

小结与适用边界

  • 优先用文件:上传二进制用请求文件、下发二进制用 FileResponse
  • JSON 无法承载原始字节,base64 是可嵌入 JSON 的通用编码,但字符膨胀使其通常不如直接传文件高效;
  • 输入方向用 model_config = {"val_json_bytes": "base64"},输出方向用 {"ser_json_bytes": "base64"},两者可同时配置在同一个模型上;
  • 配置生效后,/docs 中的 OpenAPI Schema 会自动带上 contentEncoding: base64 声明,调用方(包括自动生成的客户端)能据此正确编码请求体;
  • 参考实现:示例应用端到端测试Schema 生成定制
登录后查看全文
热门项目推荐
相关项目推荐