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,两边字段名一样但编号顺序不同,结果数据全部错乱了。后来我加了字段编号的映射表才解决。
对比速查表:一眼看区别
| 维度 | JSON | XML | Protobuf |
|---|---|---|---|
| 序列化大小 | 中等(~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 迁移时画的标准适配流程。你可以把它打印出来贴在工位上——反正我贴了,效果很好。
兼容性设计的小套路
说了这么多差异,实际操作中怎么落地?我总结了几条实用原则:
✅ 核心原则
- 统一接入层,差异化解析:在 SDK 的入口处做一个格式检测(看 Content-Type 或报文首字节),然后路由到不同的解析器。解析器内部统一转成语言自带的对象或中间结构。
- 永远保留版本号:无论是 RESTful 的 URL path(
/v2/...),还是报文里的version字段,务必带一个版本标识。这样你改字段编号或删除字段时,旧客户端不会挂。 - 字段映射表不能省:尤其从 XML 转 Protobuf 时,写一张 YAML 或 JSON 映射表,把源字段、目标字段、类型、默认值、转换规则都写清楚。我在一个项目里就因为没写映射表,改一个字段花了 3 天排查。
- 预留 unknown field 降级:Protobuf 3 默认会丢弃未知字段,但你可以通过
google.protobuf.JsonFormat的ignoringUnknownFields(true)让它静默通过。千万别让未知字段直接抛异常,生产环境会炸。
💡 小提示
如果你要同时支持 JSON 和 XML,建议用 Jackson 的 XmlMapper + ObjectMapper 双解析器,它们共用一套 Java Bean(通过 @JsonProperty + @JacksonXmlProperty 区分别名)。这样你只需要维护一套业务类。