第三章:数据类型与结构——数据映射的基石

聊到数据映射,很多人一上来就扎进复杂的转换逻辑里。我个人习惯是,先搞清楚你手里拿的是什么类型的数据。说白了,数据类型就是数据的「身份证」,结构就是它的「骨架」。搞不清楚这两样,映射肯定要出乱子。

我在项目中遇到过不少次,就因为没注意一个字段是字符串还是整型,结果整个映射链路都崩了。嗯,咱们今天就把这块地基打牢。

3.1 基本数据类型:最熟悉的陌生人

基本数据类型,每个程序员都认识。但在数据映射场景下,它们有一些特别需要注意的地方。

3.1.1 整型(Integer)

整型就是整数,比如 1、100、-50。映射时最常遇到的问题是什么?精度丢失和类型不匹配。

核心要点:不同语言、不同系统的整型范围可能不一样。Java 的 int 是 32 位,long 是 64 位。JavaScript 的 Number 是 64 位浮点,整型安全范围只有 -2^53 到 2^53。

举个例子,你从数据库读出一个 ID 是 12345678901234567890,这已经超出了 JavaScript 的安全整数范围。映射到前端时,如果不做处理,这个 ID 就变了。我曾经因为这个 bug 排查了一整个下午。

// 错误示例:直接映射
let id = 12345678901234567890; // 变成 12345678901234567000

// 正确做法:使用字符串传递大整数
let idStr = "12345678901234567890";

我的建议:跨系统传递 ID 或金额时,优先用字符串。别嫌麻烦,这能省掉你后面无数个深夜排查的时光。

3.1.2 浮点型(Float/Double)

浮点型映射是另一个重灾区。你想想看,0.1 + 0.2 在计算机里不等于 0.3,这是所有程序员都懂的梗。但在数据映射中,这个坑特别隐蔽。

我记得有一次做金融系统的数据对接,上游传过来一个金额字段是 19.99,下游系统收到后变成了 19.990000000000002。虽然只差了一点点,但在对账环节直接报错。

避坑指南:我曾经因为浮点精度问题,导致一个报表系统连续三天数据对不上。后来发现是映射时没有做精度控制。记住:金额类数据,要么用整数(分),要么用 Decimal 类型。

// 浮点映射时的精度控制
// 保留两位小数
let price = 19.99;
let mappedPrice = Math.round(price * 100) / 100; // 19.99

// 或者使用 toFixed,但注意返回的是字符串
let priceStr = price.toFixed(2); // "19.99"

3.1.3 字符串(String)

字符串看起来简单,但映射时要注意编码、空白字符、大小写等问题。说白了,字符串是最容易「看起来一样,实际上不一样」的数据类型。

问题类型 示例 映射策略
编码不一致 UTF-8 vs GBK 统一转为 UTF-8
空白字符 "hello" vs " hello " trim() 处理
大小写 "Hello" vs "hello" 统一小写或大写
特殊字符 换行符、制表符 转义或过滤

小技巧:字符串映射时,我习惯先做一次「清洗」——去掉首尾空格,统一编码,再去做匹配。这步看似多余,但能解决 80% 的字符串映射问题。

3.1.4 布尔型(Boolean)

布尔型只有 true 和 false 两个值,但映射时却花样百出。有的系统用 0/1,有的用 "Y"/"N",还有的用 "是"/"否"。

为什么会这样?因为很多老系统在设计时,布尔字段就是用字符或数字存的。映射时就需要做一次转换。

// 布尔映射的常见模式
function mapBoolean(value) {
  if (typeof value === 'boolean') return value;
  if (typeof value === 'number') return value === 1;
  if (typeof value === 'string') {
    return ['true', '1', 'yes', 'y', '是'].includes(value.toLowerCase());
  }
  return false; // 默认值
}

3.2 复杂数据类型:数组、对象与 Map

基本数据类型搞定了,接下来就是组合拳。数组、对象、Map 这些复杂类型,映射起来要考虑的东西就多了。

3.2.1 数组(Array)的映射策略

数组映射的核心问题有三个:顺序、去重、嵌套。

  • 顺序映射:源数组的第 i 个元素映射到目标数组的第 i 个元素。这种最简单,但要注意数组长度是否一致。
  • 条件映射:只映射满足条件的元素。比如只映射年龄大于 18 的用户。
  • 转换映射:每个元素经过转换后形成新数组。比如把用户对象数组映射成用户名数组。
// 数组映射示例
const users = [
  { id: 1, name: '张三', age: 25 },
  { id: 2, name: '李四', age: 17 },
  { id: 3, name: '王五', age: 30 }
];

// 条件映射 + 转换映射
const adultNames = users
  .filter(user => user.age >= 18)
  .map(user => user.name);
// 结果: ['张三', '王五']

注意:数组映射时,我建议先做「扁平化」处理。如果数组嵌套太深,映射逻辑会变得极其复杂。能用 flat() 就先拍平,不行就递归处理。

3.2.2 对象(Object)的映射策略

对象映射是最常见的场景。说白了,就是把一个对象的属性映射到另一个对象的属性上。但这里有个关键问题:属性名可能不一样,结构也可能不一样。

我在项目中遇到过最头疼的情况是:上游系统返回的是一个嵌套 5 层的 JSON 对象,下游系统需要的是一个扁平的结构。这时候就需要做「对象展平」映射。

// 对象映射示例:嵌套转扁平
const source = {
  user: {
    name: '张三',
    contact: {
      email: 'zhangsan@example.com',
      phone: '13800138000'
    }
  }
};

// 目标结构:扁平
const target = {
  user_name: source.user.name,
  user_email: source.user.contact.email,
  user_phone: source.user.contact.phone
};

我的经验:对象映射时,我习惯先画一张「映射表」。左边是源字段路径,右边是目标字段路径。别偷懒,画出来之后,代码写起来快得多。

3.2.3 Map 的映射策略

Map 类型在数据映射中越来越常见,尤其是在处理键值对数据时。Map 和对象的区别在于:Map 的键可以是任意类型,而且有顺序保证。

Map 映射的核心策略:

  • 键映射:把源 Map 的键映射成目标 Map 的键
  • 值映射:对值做类型转换或计算
  • 过滤映射:只保留需要的键值对
// Map 映射示例
const sourceMap = new Map([
  ['name', '张三'],
  ['age', 25],
  ['email', 'zhangsan@example.com']
]);

// 键映射 + 值映射
const targetMap = new Map();
const keyMapping = {
  'name': 'userName',
  'age': 'userAge',
  'email': 'userEmail'
};

for (const [key, value] of sourceMap) {
  if (keyMapping[key]) {
    targetMap.set(keyMapping[key], value);
  }
}

3.3 数据结构的映射策略总结

说了这么多,其实核心就一句话:先搞清楚类型,再设计映射策略。我整理了一个简单的对照表,方便你快速查阅。

源数据类型 目标数据类型 映射策略 注意事项
整型 字符串 直接转换 注意大整数精度
浮点型 整型 四舍五入或截断 明确舍入规则
字符串 布尔型 值映射表 处理大小写和空白
数组 对象 索引映射 注意数组越界
对象 Map 键值对映射 处理嵌套结构
Map 数组 遍历转换 保持顺序

最后提醒一句:数据类型和结构的映射,没有银弹。每个场景都有它的特殊性。我见过太多人拿着一个「万能映射工具」到处套,结果出了 bug 都不知道怎么排查。我的建议是:理解底层原理,然后针对具体场景写具体的映射逻辑。虽然看起来慢,但长远来看,这是最稳的路。

好了,这一章的内容就到这里。下一章我们会聊更进阶的话题——复杂业务场景下的映射策略。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。