3、数据格式适配:JSON/XML互转、Protobuf适配、数据模型映射、字段映射与转换规则
数据格式适配,说白了就是让两个系统能「听懂对方说话」。我做了这么多年接口适配,发现大部分联调事故都出在这一步。你想想看,A系统吐出来的是JSON,B系统只认XML,中间没人做翻译,那不就鸡同鸭讲了吗?
这一章,我带你把这几个核心问题捋清楚:JSON和XML怎么互转、Protobuf这种二进制格式怎么适配、数据模型怎么映射、字段名不一样怎么办。嗯,都是实战中天天要碰的东西。
3.1 JSON与XML互转:最基础的翻译官
JSON和XML的互转,我个人的习惯是「先拆结构,再转语法」。两者本质都是树形结构,只是表达方式不同。
核心差异点:
- JSON有数组(Array),XML没有原生数组,需要用重复标签模拟
- XML有属性(Attribute),JSON没有,需要约定映射规则
- JSON的键值对天然对应XML的标签-文本关系
一个典型场景:我在项目中遇到过,上游系统返回的JSON里有个字段叫"phone_numbers",是个数组。下游XML系统死活解析不了,因为它的XSD里定义的是单个"phone"标签。最后我们加了个转换规则:数组展开成多个同名标签。
互转示例:
// JSON 原始数据
{
"user": {
"name": "张三",
"age": 28,
"phones": ["13800138000", "13900139000"]
}
}
// 转换后的 XML
<user>
<name>张三</name>
<age>28</age>
<phones>13800138000</phones>
<phones>13900139000</phones>
</user>
我的小技巧:做JSON转XML时,建议先定义好「数组展开策略」。是全部展开?还是只展开特定路径?我一般会在配置中心里维护一个白名单,避免误展开。
3.2 Protobuf适配:二进制世界的硬核翻译
Protobuf这东西,说白了就是Google搞的一套高效序列化协议。它比JSON小,比XML快,但可读性差。适配Protobuf,我踩过不少坑。
适配要点:
- 必须要有.proto文件,否则你连字段名都猜不出来
- 字段编号(Field Number)是硬编码的,不能随便改
- 默认值问题:int类型默认是0,bool默认是false,这会导致「没传」和「传了0」无法区分
我曾经踩过的坑:有一次对接支付系统,对方用Protobuf,我这边用JSON。对方返回的金额字段是0,我以为是没传,直接跳过了。结果那笔交易实际金额就是0元(优惠券抵扣后)。后来我加了一条规则:所有数值字段必须显式判断是否存在,不能依赖默认值。
Protobuf转JSON的典型映射:
// .proto 定义
message Order {
int32 id = 1;
string name = 2;
float price = 3;
bool paid = 4;
}
// 序列化后的二进制 -> 转成 JSON
{
"id": 1001,
"name": "笔记本电脑",
"price": 5999.00,
"paid": true
}
我建议:如果系统间频繁做Protobuf和JSON互转,最好维护一个「字段编号-字段名」的映射表。这样即使.proto文件更新了,也能快速定位问题。
3.3 数据模型映射:从「鸡同鸭讲」到「心有灵犀」
数据模型映射,说白了就是两个系统对同一件事物的不同描述方式。比如A系统叫"customer",B系统叫"client",其实都是客户。
映射策略:
- 一对一映射:字段名不同,但含义完全一样。比如"userName" ↔ "username"
- 一对多映射:一个字段拆成多个。比如"fullName"拆成"firstName"和"lastName"
- 多对一映射:多个字段合并成一个。比如"province"+"city"合并成"address"
- 值映射:含义相同但枚举值不同。比如A系统的"1"代表男,B系统的"M"代表男
一个真实案例:我做过一个电商系统的适配,A系统用"order_status"字段,值是"0,1,2";B系统用"status_code",值是"PENDING,PAID,SHIPPED"。我们建了一个映射表,把数字和字符串一一对应起来。嗯,这个表后来成了整个团队的「圣经」。
映射表示例:
| 源字段 | 目标字段 | 映射规则 | 示例 |
|---|---|---|---|
| userName | username | 直接映射 | "张三" → "张三" |
| fullName | firstName + lastName | 按空格拆分 | "张三丰" → "张" + "三丰" |
| orderStatus (0/1/2) | statusCode (PENDING/PAID/SHIPPED) | 枚举映射 | 0 → "PENDING" |
| price (分) | amount (元) | 除以100 | 59900 → 599.00 |
3.4 字段映射与转换规则:适配的灵魂
字段映射,说白了就是「翻译字典」。但光有字典还不够,还得有「语法规则」。我总结了一套转换规则体系,分享给你。
转换规则分类:
- 直接映射:字段名相同,类型相同,直接赋值
- 重命名映射:字段名不同,但含义相同,改个名字就行
- 类型转换:比如字符串转数字、日期格式转换
- 计算映射:通过公式计算得到新值,比如"总价 = 单价 × 数量"
- 条件映射:根据条件决定映射结果,比如"如果年龄大于18,则isAdult=true"
我曾经犯过的错:有一次做日期格式转换,源系统是"yyyy-MM-dd HH:mm:ss",目标系统是"yyyy/MM/dd"。我直接用了字符串截取,结果遇到跨年数据就出错了。后来老老实实用了标准的时间库来解析和格式化。
转换规则配置示例:
// 一个简单的规则引擎配置
{
"mappings": [
{
"source": "create_time",
"target": "createdAt",
"type": "date_format",
"from": "yyyy-MM-dd HH:mm:ss",
"to": "yyyy/MM/dd"
},
{
"source": "price_in_cents",
"target": "price",
"type": "divide",
"value": 100
},
{
"source": "status",
"target": "statusText",
"type": "enum_map",
"mapping": {
"0": "待支付",
"1": "已支付",
"2": "已发货"
}
}
]
}
我个人的最佳实践:把转换规则做成可配置的,不要硬编码在代码里。这样业务变了,改个配置文件就行,不用重新部署。我一般用YAML或者JSON来维护这些规则,配合一个简单的规则引擎来执行。
3.5 知识体系总览
下面这张图,是我梳理的数据格式适配知识体系。你可以把它当成一张「地图」,遇到适配问题先看属于哪一类,再找对应的解决方案。
数据格式适配这件事,说难不难,说简单也不简单。关键是要把规则梳理清楚,把映射关系维护好。我做了这么多年,最大的体会就是:不要相信「这个字段肯定不会变」这种话。业务一变,字段名、类型、枚举值都可能跟着变。所以,把规则做成可配置的,才是长久之计。
嗯,这一章的内容就到这里。记住,适配的核心不是技术,而是对业务的理解。你理解了业务,自然就知道该怎么映射、怎么转换了。