任务架构

Android Protobuf 与 JSON 互转:未知字段和数值精度

讨论 Protobuf binary ↔ ProtoJSON 桥接的信息损失与实际生成 runtime;区别通用模型 JSON schema 校验,不把可读 JSON 宣称为无损或权威备份。

审核状态: 内容已校验;中文稿供用户审核 · 2026-10-08 · Fact IDs: ANDROID-PROTOBUF-JSON-ROUNDTRIP-2026-10, RAG-01, ANDROID-PRIVACY-01

直接回答

Protocol Buffers binary 和 ProtoJSON 是不同 wire 表示,兼容特性也不同。Proto3 生成的 message 若通过保留 unknown-field 状态的 API 解析并重新序列化,可以保留未知 binary field。转成 JSON 后不能以同样方式保留 unknown field;JSON parser/printer 使用字段名和特定 JSON 映射,新版 binary producer 增加的内容可能被旧版 JSON 转换拒绝或丢弃。把已知字段逐项复制到新 message,也可能丢掉旧 message 中保留的 unknown binary field。因此不能只说“protobuf 会保留字段”,必须写清格式和 API 流程。 ProtoJSON 是规定的类型映射,不是任意 map 的通用美化器。字段名通常映射成 lowerCamelCase,parser 可接受规范指定的字段名形式;枚举通常用符号名;bytes 表示为 base64 字符串;64 位整数族默认输出十进制字符串,避免 binary64 JSON 消费者精度不足。Well-known type 还可能有特定表示。若消费者把所有值塞进松散 JSON object,这些区别会消失,也可能造成 key 碰撞。 在所有语言边界都要保持 64 位值精确。常见 JSON 数值模型无法精确表达所有大于 2^53 的整数。ProtoJSON 因此默认把 `int64`、`uint64`、`sint64` 和 fixed-width 64 位整数输出为十进制字符串。部分 parser 接受带引号和不带引号两种形式,但通用 JavaScript consumer 可能把裸数字解析得不精确。测试 2^53 附近、带符号边界、无符号边界、合法负值与指数写法;不能先转成 `Double` 或 JavaScript `Number`,再写回 message。 bytes 和 string 不是可互换字段。ProtoJSON 中 protobuf `bytes` 表示为 base64 文本;`string` 则是 Unicode 文本并受 protobuf UTF-8 规则约束。若应用为了“便于阅读”将 base64 解码成字符串,任意字节可能被破坏。保留有类型的 bytes 或明确标记的 base64。JSON `null` 也涉及 presence:一般字段的 ProtoJSON null 表示未设置;wrapper 和某些 well-known type 有专门映射。显式 presence 与默认值可能不同,所以要测试 message descriptor 和生成 API。 未知 enum 值与未知字段需要按 schema 版本测试。Proto3 enum openness、语言 runtime 行为以及导入的 proto2 closed enum 都可能影响数字值如何呈现或保留。ProtoJSON 默认输出枚举名,但部分选项允许输出数字;旧 schema consumer 可能拒绝新符号名。binary schema 兼容不意味着 JSON 字段改名或删除 enum 值也兼容。按 schema 规则 reserve 已删除 field number 和 name,并用准确生成代码测试旧/新写入与读取组合。 桥接前要定义用途。如果 JSON 只为诊断,应明确标注为可能有损,不能再作为权威备份导回。如果 JSON 必须是互通合同,要对其版本化,固定 `.proto` descriptor 与 protobuf runtime,记录 JSON printer/parser 选项和未知字段策略。如果必须向前兼容 binary,应保留原始 binary bytes,或使用经过验证、能保留 unknown field 的 message-oriented merge/copy 路径。不能先 binary→JSON→binary,再称原消息完整无损。 Android build 可能使用不同生成 runtime 和 API。完整 protobuf Java、lite runtime、generated message API、JSON format utility 和自定义 serializer 的反射能力或 unknown-field API 并不相同。没有检查 resolved dependency 前,不要推荐 `JsonFormat` 对所有 Android artifact 都存在。记录 protoc/compiler 版本、生成模式、protobuf runtime 坐标、Android min API、parser/printer 选项和实际调用方法。对准确 production 配置编译并实跑;一种 runtime 文档不能替代另一种构建结果。 基于版本化 `.proto` schema 构造夹具。先用新 schema 写入新 field 的 message,再用旧 schema 解析,分别经过 binary-only parse/serialize 和 JSON bridge,检查各自保留内容。加入 2^53 附近的 64 位边界值、非 UTF-8 octets、枚举名与未知数字、未设置和默认值、显式 null、转换字符串 key 后可能冲突的 map,以及实际使用的 well-known type,如 Timestamp、Any、Duration、FieldMask。semantic message field 和 wire bytes 分开比较;除非协议有定义,否则 protobuf binary serialization 也不是 canonical byte format。 报告每条转换路径的矩阵:binary parse→binary serialize、generated-message copy、binary→ProtoJSON、ProtoJSON→message、generic JSON object 转换和自定义 normalization。逐项写清 unknown-field 保留、数值精度、presence、enum 处理和错误结果。JSON 语法有效不能证明可往返等价、具备权限或语义正确。本文是互操作方法,不表示 Cove 保存 protobuf 记录或使用某个 Android runtime。

旧 Android 客户端编辑了新版本发来的 message

binary protobuf message 含有已安装客户端 schema 不认识的新字段。应用将 message 转 JSON 供查看,再按可见已知字段重建 message 并保存。即使 binary parser 最初接收了 message,新字段仍可能在 JSON 环节消失。

决策

先确定 JSON 只是诊断展示还是权威互通格式。若要求未知 binary field 存活,应保留原始 bytes 或走经过测试的保留路径;不能经 ProtoJSON 往返却承诺不丢失。

预期结果

导入导出报告按每条转换路径明确保留/丢弃的信息,并固定对应 schema 与 runtime。

需要收集的证据

只有将以下证据与同一模型文件和测试运行关联起来,结论才可复核:

  • ANDROID-PROTOBUF-JSON-ROUNDTRIP-2026-10:Proto3 binary message API 可保留未知字段;ProtoJSON 不以相同方式传递未知字段,并默认把 64 位整数输出为十进制字符串。
  • RAG-01:数据摄取、索引存储、prompt 构造和 generation 是独立阶段;序列化往返结果须在相应使用阶段单独验证。
  • ANDROID-PRIVACY-01:应用的数据访问和用户控制责任独立于推理位置。

任务质量需要有版本记录的语料和验收规则;流畅的单条样例不等于准确度结果。

实施流程

  1. 绘制实际 generated runtime 路径. 记录 `.proto` 修订、protoc 版本、full/lite 生成模式、Android resolved runtime、parser/printer 选项和每个转换 API。编译并调用准确方法,不从 protobuf 通用文档猜 API 一定存在。
  2. 确定哪种表示具权威性. 需要 forward-compatible opaque interchange 时,使用经验证能保留未知字段的 binary message 路径。除非产品有版本化 ProtoJSON 合同并接受其损失边界,否则人类可读 JSON 标成诊断视图。
  3. 保持类型映射精确. 在无法用 JSON number 精确表示所有整数的语言中,按 ProtoJSON 使用十进制字符串承载 64 位整数。保留 bytes/base64、枚举策略、显式 presence、null 语义、map key 类型和 well-known type 映射。
  4. 覆盖新旧 schema 转换. 构造新 writer 写入未知 field/enum value、旧 reader 消费的 fixture,并尝试各 binary 与 JSON 转换。分别比较已知字段、可观测 unknown-field 状态、错误、数值及原始 bytes。
  5. 标明有损路径和限制. 按路径公布 binary parse/serialize、message copy、ProtoJSON print/parse、generic JSON 转换与自定义归一化结果。不能仅凭 JSON 可读性声称语义相等或字节规范化。

让每个阶段的状态都可观察。失败信息应指出阶段、模型、运行时和恢复动作,同时避免记录用户私密内容。

验收标准

  • 未知字段只在实际测试的 binary message 路径中声称保留;ProtoJSON 和逐字段 copy 被作为可能有损路径测试。
  • 准确 Android protobuf runtime、生成模式、编译器、转换 API 与选项在声明 build 中编译并运行。
  • 夹具保持 64 位整数精度,区分 bytes/text,覆盖 enum 与 presence/null,并记录 well-known type 和 map-key 转换限制。
  • 新旧 schema 兼容性按 binary 与 JSON 分开报告,并覆盖字段名和枚举名变化。
  • 有损诊断 JSON 不会被误当作权威备份或 message 语义等价保证。

决策对照表

维度选择或证据解读方式
Binary parse 后 binary serialize使用同一种 message 表示与经验证保留 unknown field 的 runtime 路径。受支持的 proto3 message API 可保留未知字段;仍要核实具体 runtime,避免只重建已知字段。
ProtoJSON print 再 parse用作版本化 JSON 合同或明确标注的诊断视图。JSON 使用字段名与类型映射,不能像 binary 一样传播未知字段。
64 位整数在通用 JSON consumer 间保持 ProtoJSON 十进制字符串。binary64 数值模型可能静默损失超过 2^53 的整数精度。
Bytes、枚举和 null遵守 ProtoJSON 对具体类型和 presence 的映射。Base64、枚举名/数字以及 unset/default 不能按通用 JSON 值互换。
逐项复制已知字段到新 message仅在接受丢弃 unknown field 的迁移决策时使用。逐字段重建会漏掉原始 parsed message 保留的不透明字段。

需要避免的失败模式

  • 笼统说 “protobuf 保留未知字段”,却不写 binary/JSON 表示和具体生成 API。
  • 把 `int64` 经 JavaScript Number 或 Double 中转造成 2^53 以上整数精度损失。
  • 把 bytes 当 UTF-8 文本、混淆枚举名字/数值,或遗漏 null 与 presence 的语义。
  • 未经 Android resolved artifact 编译验证,就假定反射或 JsonFormat API 存在于 lite runtime。

还要避免静默回退、版本未固定、缺少取消处理,以及把不同配置的结果混在一起下结论。失败运行也应与成功记录一并保存。

最小复现记录

层级记录内容
设备厂商、型号、芯片、内存档位、操作系统版本
软件应用版本和 Git 提交号
模型系列、变体、版本、格式、文件长度、摘要、量化方式
运行时名称、版本、请求后端及实际后端证据
工作负载样例版本、输入摘要、提示词摘要、输出策略
结果完成、失败、取消、回退及保护隐私的诊断信息

发布检查表

  • ☐ 主要搜索问题已回答,且没有未经支持的数字。
  • ☐ 所有模型文件和运行时版本均已固定。
  • ☐ 代表性任务和失败处理策略均有说明。
  • ☐ 生命周期、取消、清理和回退均已测试。
  • ☐ 用户内容与网络边界均有说明。
  • ☐ 结论只适用于被记录的配置。
  • ☐ 新增测试结果时链接到原始方法或证据。

来源与相关证据

中文部署与排错专题见。奇连 AI 端侧专题。

常见问题

Protocol Buffers 会保留未知字段吗?

Proto3 binary message API 可以在解析后重新序列化时保留未知字段。但并非每种转换都保留:ProtoJSON 不会以相同方式传递 unknown field,逐字段复制也可能丢弃它们。应测试准确 runtime 路径。

ProtoJSON 为什么把 int64 输出为字符串?

许多 JSON consumer 以 binary64 表示 number,无法精确表示所有大于 2^53 的整数。ProtoJSON 默认输出 64 位整数族的十进制字符串,避免静默精度损失。

protobuf bytes 能转换为 UTF-8 文本展示吗?

通常不能。`bytes` 可包含任意 octet。ProtoJSON 用 base64 表示;只有应用另行验证文本编码并保留原始 bytes 后,才适合派生文本视图。

JSON 往返会得到 canonical protobuf message 吗?

不会。ProtoJSON 有独立映射,也可能丢 unknown field 或改变 presence 表示;binary serialization 本身也不自动是 canonical 字节格式。协议必须自行定义比较要求。