4、原系统接口协议分析(一):请求与响应格式(JSON、XML、Protobuf)的差异与兼容性考虑

做接口迁移,第一个碰到的坑就是协议格式不统一。很多老系统用的是 XML,新团队喜欢 JSON,后端的同学又悄悄用起了 Protobuf。说白了,三种格式各有脾气。你得先摸清楚它们的脾气,才知道迁移时怎么哄着它干活。

我参与过一个金融交易系统的 SDK 适配。原系统所有接口都是 XML,对端要求我们输出 Protobuf。一开始我直接硬转换,结果字段类型全乱套。嗯,这事让我重新把三种底层格式的差异翻了个底。

为什么非得搞懂格式差异?

你想想看,一条 HTTP 请求从客户端发起,到服务端处理,要跨三层解析:

  • 传输层:字节流怎么组装?
  • 结构层:字段怎么取、怎么填?
  • 类型层:数字是 int32 还是 float?浮点精度够不够?

这三层任何一个对不上,接口就崩。而 JSON、XML、Protobuf 在这三层的处理方式,差别非常大。

JSON:轻量但类型松散

JSON 是我个人最喜欢的调试格式。可读性好,浏览器直接看,调试时 duang 一下就知道问题在哪。但它的类型系统非常「宽松」——说白了就是没类型:

  • 数字不分 int32 / int64 / float,全部写成 123 或 12.3
  • 布尔值可能被序列化成字符串 "true",也可能被写成 true(看库的实现)
  • 数组空值可能被降级成 [] 或干脆删除字段

我在一个支付项目里就遇到过:对方 JSON 里的金额字段有时是 100.00,有时是 100,我这边强类型解析直接挂掉。所以,如果你要跟 JSON 打交道,一定要在序列化时显式指定字段类型映射。

XML:冗余但可扩展

老系统用 XML 的太多了。SOAP、REST早期、甚至一些配置项都是 XML。它的优点是什么?能用 XSD 强约束,还能加命名空间、注释、属性。缺点也很明显:

  • 自描述的结构让包体积膨胀 30%~50%
  • 解析速度比 JSON 慢 2~3 倍(尤其用 DOM 解析时)
  • 标签嵌套深度容易失控

我记得有一次对接一个十年前的银行接口,XML 响应里居然混了 CDATA 段和命名空间别名,我调了整整一个下午才把字段提取出来。唉,这事儿让我养成了一个习惯:迁移前先拿样本报文跑一遍解析库。

Protobuf:高效但需要编译

Protobuf 是现在微服务里最火的序列化格式。体积小,解析快,类型严格。但它有个门槛:必须提前定义 .proto 文件并生成代码。这就带来了兼容性问题——

  • 字段编号不能改,改了旧版本就认不出来了
  • 删除字段要标记 reserved,否则旧二进制里会出脏数据
  • 不支持动态 field,所有字段得在 .proto 里声明好

我在一个游戏排行榜的 SDK 适配里吃过亏:原系统是 JSON,新系统用 Protobuf,两边字段名一样但编号顺序不同,结果数据全部错乱了。后来我加了字段编号的映射表才解决。

对比速查表:一眼看区别

维度JSONXMLProtobuf
序列化大小中等(~500B/百字段)大(~1KB/百字段)小(~200B/百字段)
解析速度快(~10μs)慢(~30μs)极快(~3μs)
类型系统弱(动态)中(XSD可选)强(编译时)
可读性高高(但冗余)低(二进制)
跨语言支持原生支持原生支持需生成代码
版本兼容宽容(忽略未知字段)宽松(可选标签)严格(必须遵守 .proto)
元数据支持无命名空间、属性、注释选项、oneof、map

兼容性设计:我踩过的三个坑

⚠️ 我曾经犯的错误(避坑指南)

  • 坑一:无视字段编号的映射 – 从 JSON 转 Protobuf 时,字段名一样但编号不同,直接导致整段数据变成乱码。正确做法:提前用 Protobuf 的 json_name 选项把字段名对齐。
  • 坑二:浮点精度丢失 – XML 里的 3.1415926535 转成 JSON 只有小数点后 6 位,再转 Protobuf 的 float 只剩 7 位有效数字。我后来强制所有金额字段都用字符串传递,到业务层再转 decimal。
  • 坑三:缺少版本号导致解析失败 – 原系统接口升级后加了新字段,但没在报文里带版本号,下游解析时直接报 UnknownField 异常。现在我会在所有请求包里加一个 version 字段,服务端根据版本号选择解析策略。

一个实用的适配流程(SVG 导图)

下面这张图是我在做 SDK 迁移时画的标准适配流程。你可以把它打印出来贴在工位上——反正我贴了,效果很好。

请求与响应协议格式适配决策流程 协议格式适配决策流程 源系统请求(原格式) 判断格式 JSON → 统一对象 XML → 统一对象 Protobuf → 统一对象 统一内部业务对象 适配策略 • 类型映射表维护 • version 版本号协商 • 未知字段降级处理 箭头方向:请求流入方向

兼容性设计的小套路

说了这么多差异,实际操作中怎么落地?我总结了几条实用原则:

✅ 核心原则

  1. 统一接入层,差异化解析:在 SDK 的入口处做一个格式检测(看 Content-Type 或报文首字节),然后路由到不同的解析器。解析器内部统一转成语言自带的对象或中间结构。
  2. 永远保留版本号:无论是 RESTful 的 URL path(/v2/...),还是报文里的 version 字段,务必带一个版本标识。这样你改字段编号或删除字段时,旧客户端不会挂。
  3. 字段映射表不能省:尤其从 XML 转 Protobuf 时,写一张 YAML 或 JSON 映射表,把源字段、目标字段、类型、默认值、转换规则都写清楚。我在一个项目里就因为没写映射表,改一个字段花了 3 天排查。
  4. 预留 unknown field 降级:Protobuf 3 默认会丢弃未知字段,但你可以通过 google.protobuf.JsonFormat 的 ignoringUnknownFields(true) 让它静默通过。千万别让未知字段直接抛异常,生产环境会炸。

💡 小提示

如果你要同时支持 JSON 和 XML,建议用 Jackson 的 XmlMapper + ObjectMapper 双解析器,它们共用一套 Java Bean(通过 @JsonProperty + @JacksonXmlProperty 区分别名)。这样你只需要维护一套业务类。