第三章:数据类型与结构——数据映射的基石
聊到数据映射,很多人一上来就扎进复杂的转换逻辑里。我个人习惯是,先搞清楚你手里拿的是什么类型的数据。说白了,数据类型就是数据的「身份证」,结构就是它的「骨架」。搞不清楚这两样,映射肯定要出乱子。
我在项目中遇到过不少次,就因为没注意一个字段是字符串还是整型,结果整个映射链路都崩了。嗯,咱们今天就把这块地基打牢。
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 都不知道怎么排查。我的建议是:理解底层原理,然后针对具体场景写具体的映射逻辑。虽然看起来慢,但长远来看,这是最稳的路。
好了,这一章的内容就到这里。下一章我们会聊更进阶的话题——复杂业务场景下的映射策略。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。