从表单、表格或接口拿到字段名后,把前后空格去掉,看起来只是一次小修正。但如果输入同时含有 "name" 和 " name " ,清洗后的名字就撞在一起。后写入的值覆盖先写入的值,程序没有报错,信息却已经丢了。
这个问题常见于导入脚本和配置转换。关键不是选哪一个值,而是让冲突在覆盖发生前变成一个明确结果。
一、保留原始输入,别急着转成对象
用条目数组表示输入,能保留顺序和同名字段。下面两行来自不同来源,含义可能也不同:
javascript
const entries = [["name", "Alice"], [" name ", "Bob"]];
如果此时直接写成对象,完全相同的键可能已经被覆盖。真正需要检查的,是原始条目经过规范化后,是否指向了同一个名字。因此清洗和构造对象最好放在同一次受控操作里。
二、让冲突带上来源
javascript
function normalizeEntries(entries) {
const seen = new Map();
const result = Object.create(null);
for (const [rawKey, value] of entries) {
if (typeof rawKey !== "string") {
throw new TypeError("字段名必须是字符串");
}
const key = rawKey.trim();
if (!key) throw new Error("字段名不能为空");
if (seen.has(key)) {
throw new Error(`字段冲突:${seen.get(key)} 与 ${rawKey}`);
}
seen.set(key, rawKey);
result[key] = value;
}
return result;
}
这里的 Map 保存的是"规范名字到原始名字"的映射。再次遇到相同规范名字时,错误信息能同时指出两处输入。它不会擅自采用第一行,也不会悄悄采用最后一行。
Object.create(null) 创建没有原型的字典,适合存放外部字段名。它不提供对象原型上的方法,所以判断是否存在某个键时,应使用 Object.hasOwn(result, key) ,不要调用 result.hasOwnProperty 。
三、空值和重复值是两种问题
两个同名字段恰好都写了空字符串,也依然属于名字冲突。值相同并不能证明这两项来自同一条记录。如果业务确实允许合并,需要另写规则:相同值是否保留一项、不同值如何处理、最后是否要向操作者报告合并数量。
同样,不要顺手把所有字段名改成小写。 userID 与 userid 是否等价,应由接收系统的字段约定决定。只去空格与统一大小写,是两种不同的转换,应该分别说明。
四、验证时覆盖边界输入
可以先用几组容易忽略的输入走一遍:正常字段、前后带空格的字段、清洗后空字符串、清洗后重名,以及名字为 __proto__ 的字段。正常组应得到完整结果;冲突组应明确失败,不能返回一个缺了值的"成功对象"。
处理大批量数据时,还应记录行号或文件名。报错只有字段名,操作者仍可能找不到需要修改的位置;加上来源后,问题才真正可处理。
规范化的目标是减少表达差异,不是替业务决定哪个值更可信。先识别冲突,再由明确规则解决,通常比导入成功后追查数据为什么少了一列更省时间。
