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 知识体系总览

下面这张图,是我梳理的数据格式适配知识体系。你可以把它当成一张「地图」,遇到适配问题先看属于哪一类,再找对应的解决方案。

数据格式适配知识体系 数据格式适配 JSON/XML互转 Protobuf适配 数据模型映射 字段映射与转换 数组展开策略 属性映射规则 命名空间处理 字段编号映射 默认值处理 oneof/any处理 一对一映射 一对多/多对一 枚举值映射 直接/重命名 类型转换 计算/条件映射 核心原则:先拆结构,再转语法 规则可配置,不要硬编码

数据格式适配这件事,说难不难,说简单也不简单。关键是要把规则梳理清楚,把映射关系维护好。我做了这么多年,最大的体会就是:不要相信「这个字段肯定不会变」这种话。业务一变,字段名、类型、枚举值都可能跟着变。所以,把规则做成可配置的,才是长久之计。

嗯,这一章的内容就到这里。记住,适配的核心不是技术,而是对业务的理解。你理解了业务,自然就知道该怎么映射、怎么转换了。

可可精品资料库