news 2026/9/9 23:00:51

FastAPI 在 JSON 中内嵌 Base64 编码的二进制数据:Pydantic `bytes` 字段的校验与序列化全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastAPI 在 JSON 中内嵌 Base64 编码的二进制数据:Pydantic `bytes` 字段的校验与序列化全指南

FastAPI 在 JSON 中内嵌 Base64 编码的二进制数据:Pydanticbytes字段的校验与序列化全指南

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

JSON 只能承载 UTF-8 编码的字符串,无法直接存放原始字节,因此当应用需要"在 JSON 请求/响应里附带二进制数据(图片、文件片段、加密签名等)"时,通常先把字节编码为 Base64 字符串再放进 JSON。本文以 FastAPI 仓库中 docs_src/json_base64_bytes/tutorial001_py310.py 的完整可运行示例为核心,讲解如何借助 Pydantic 的val_json_bytesser_json_bytes模型配置,让 FastAPI 在输入时自动把 Base64 解码成bytes、在输出时自动把bytes序列化成 Base64,并给出仓库测试与 OpenAPI 生成结果的验证证据。读完你将掌握一套可直接复制的"JSON + Base64 二进制传输"方案,并理解它与文件上传/下载方案的取舍边界。

本文内容对应 FastAPI 官方文档 JSON with Bytes as Base64(中文成文,另一份同主题翻译版见 docs/ko/docs/advanced/json-base64-bytes.md)。

为什么需要在 JSON 里编码二进制:Base64 与文件方案的选择

先厘清一个核心约束:JSON 规范只允许 UTF-8 编码的字符串,没有"原始二进制"数据类型。因此想通过 JSON 传递一个 PNG 文件、一段任意字节,都必须先将字节编码成文本——最常用且具备互操作标准的就是 Base64。

不过,把二进制塞进 JSON 并不是首选方案:

  • 上传二进制应优先使用 multipart 请求文件,参见教程 请求文件(Request Files);
  • 下发二进制应优先使用流式响应,例如 自定义响应中的FileResponse

Base64 虽然能把字节编码成字符串,但它会比原始二进制多占用字符——每 3 个字节会被展开为 4 个 Base64 字符(开销约 1/3),且还需再叠加 JSON 自身的引号与转义开销,因此编码进 JSON 的传输方式在字节数上明显劣于普通文件流。只有当业务上确实必须在 JSON 内承载二进制、且无法改用文件传输时,才应当使用 Base64。典型的适用场景包括:需要把二进制嵌套进某个结构化业务报文、与第三方 JSON API 对接,或数据本身是必须放进 JSON body 的一小段字节(如签名、指纹、图标数据)。

一个跑得通的完整示例:三种模型与三个端点

仓库中随文档提供的可运行示例位于 docs_src/json_base64_bytes/tutorial001_py310.py(文件名后缀py310表明其依赖 Python 3.10+ 语法与 Pydantic v2 的model_config写法)。下面把它完整展开:

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

可以看到,三个 Pydantic 模型都声明了data: bytes字段,区别只在于model_config

  • DataInput:只配置val_json_bytes,用于接收(校验输入)
  • DataOutput:只配置ser_json_bytes,用于返回(序列化输出)
  • DataInputOutput:同时配置两者,同一模型既收又发。

应用搭建好后,/docs(Swagger UI)会自动展示接口文档。官方文档截图显示,POST /data的请求体说明中data字段会明确按 Base64 编码的 bytes 呈现,引导调用方提交形如aGVsbG8=的字符串:

输入侧:用val_json_bytes把 Base64 解码成bytes

model_config = {"val_json_bytes": "base64"}的含义是:当 Pydantic校验输入的 JSON 数据时,遇到bytes类型的字段,就把它当作 Base64 编码的字符串来解析,并在校验流程中自动完成 Base64 → 原始字节的解码。你的路径函数拿到的body.data已经是真正的bytes对象,无需手动调用base64.b64decode()

POST /data发送如下请求:

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

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

Pydantic 会把data字段的 Base64 字符串解码为b"hello",于是示例端点body.data.decode("utf-8")得到"hello",返回:

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

这一行为在仓库测试 tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py 的test_post_data中得到了端到端验证:提交"SGVsbG8sIFdvcmxkIQ=="(即Hello, World!的 Base64),断言返回{"description": "A file", "content": "Hello, World!"}

输出侧:用ser_json_bytesbytes序列化成 Base64

model_config = {"ser_json_bytes": "base64"}作用于生成 JSON 响应的过程:Pydantic 在序列化模型时,会把bytes字段编码为 Base64 字符串后再写入 JSON,调用方拿到的是可安全放入 JSON 的文本。

示例中GET /data的返回类型注解为DataOutput,端点内部构造了DataOutput(description="A plumbus", data=b"hello"),经序列化后实际响应为:

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

仓库测试中的test_get_data恰好断言了这一结果(注意这里的data是 Base64 编码的"aGVsbG8=",而不是明文"hello"),说明输出配置确实生效。

同一模型兼顾输入与输出

如果同一个模型既要接收 Base64 编码的 JSON 输入、又要以 Base64 输出 JSON,只需在model_config里同时写入两个配置项(即上面的DataInputOutput)。POST /data-in-out端点直接回显收到的 body:

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

由于输入经val_json_bytes解码为bytes、输出又经ser_json_bytes重新编码为 Base64,响应中的data与请求保持一致,实现"收到什么、返回什么"的透明回显。test_post_data_in_out验证的正是这种往返一致性。

双端一致的 OpenAPI 文档与机器可读描述

bytes字段交给 Pydantic 后,FastAPI 生成的 OpenAPI 也会随之变化。上述三个模型(DataInputDataOutputDataInputOutput)在/openapi.json中的data字段都被描述为(见仓库测试中的 OpenAPI snapshot 断言):

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

也就是说,OpenAPI 3.1 会通过contentEncoding: base64contentMediaType: application/octet-stream标准字段,向 Swagger UI、代码生成器等下游工具精确声明该字段承载的是 Base64 编码的字节流——这正是 Swagger UI 能向用户提示 Base64 输入样例、以及自动生成的客户端能正确编码二进制数据的原因,也让 API 契约具备机器可读的准确语义。

如何运行与验证(基于仓库现有测试)

该示例依赖 Python 3.10+(测试用needs_py310标记门控,参见 tests/utils.py)与 Pydantic v2 的model_config语法。可以直接用仓库的测试来验证全部行为:

pytest tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py -v

test_tutorial001.py内部通过importlib动态加载 docs_src/json_base64_bytes/tutorial001_py310.py,并使用 FastAPI 的TestClient依次覆盖四条路径:

  • test_post_data:Base64 请求输入 → 服务端解码出可读文本;
  • test_get_databytes返回值 → Base64 序列化输出;
  • test_post_data_in_out:同一模型完成解码与再编码的往返回显;
  • test_openapi_schema:断言/openapi.json中三个模型及data字段的contentEncoding: base64等元数据。

若想本地手动体验,先安装fastapiuvicorn,然后以该示例文件为入口启动:

pip install "fastapi" "uvicorn" uvicorn docs_src.json_base64_bytes.tutorial001_py310:app --reload

随后打开http://127.0.0.1:8000/docs即可在 Swagger UI 上直观看到三个端点的 Base64 请求体提示,或直接访问http://127.0.0.1:8000/openapi.json查看生成的机器可读契约。

小结与适用边界

在 FastAPI 中把二进制数据放进 JSON,正确姿势是:声明bytes类型的 Pydantic 字段,并在model_config中按需启用val_json_bytes/ser_json_bytes"base64"——输入侧自动解码、输出侧自动编码、两端共用同一模型也完全支持,OpenAPI 文档与客户端生成都会随之获得标准化的 Base64 语义。

但请始终记得文档反复强调的取舍:Base64 会让二进制膨胀约 1/3 且无法利用流式传输,在传输大文件类二进制时应优先考虑 Request Files 与 FileResponse;只有确实需要在 JSON 结构化报文内部携带小段二进制、且无法改用文件时,这一方案才是更优解。

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 23:00:02

HagiCode Desktop混合分发架构:如何用P2P+HTTP解决大文件下载难题

如果你经常要分发几百MB、几个GB甚至几十GB的安装包、数据集、固件或者游戏客户端,大概率遇到过这种场景:服务器带宽明明不低,下载的人一多,速度立刻掉到几十KB/s;用网盘中转,等半天还容易断线;…

作者头像 李华
网站建设 2026/9/9 22:59:58

JavaFX系统托盘与中文乱码实战:Jfoenix应用开发

简介:JavaFXJfoenix系列学习笔记(十)配套源码,面向需要掌握桌面托盘交互与中文乱码处理的JavaFX开发者。内容基于Jfoenix Material Design组件库,演示通过java.awt.SystemTray实现系统托盘图标、关闭窗口后驻留以及点击…

作者头像 李华
网站建设 2026/9/9 22:59:54

TestNG监听器实战:Selenium自动化测试结果分析、截图与报告定制

开头(≥200字) 做Selenium WebDriver自动化测试的人,十有八九都会在某个阶段被同一个问题卡住:用例写了一大堆,跑起来也能看到绿红结果,可一旦用例数量上了三位数、四位数的量级,光靠控制台输出…

作者头像 李华
网站建设 2026/9/9 22:59:05

PHP异步系统必备:对账与重试机制的设计与实践

1. 为什么说对账和重试是异步操作的"安全带"1.1 异步的本质是把失败推迟了,而不是把失败消灭了很多PHP项目走到一定规模之后,一定会碰到一道坎:异步化。用户注册后发通知邮件、订单支付后推送履约消息、报表生成后回调前端轮询接口…

作者头像 李华
网站建设 2026/9/9 22:58:40

Terraform 如何从源码构建可执行文件并设置 ldflags 与 CGO_ENABLED

Terraform 如何从源码构建可执行文件并设置 ldflags 与 CGO_ENABLED 【免费下载链接】terraform Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configur…

作者头像 李华