在前后端数据交互或者本地数据处理场景中,JSON是最常用的数据格式之一。很多时候由于接口版本迭代、数据来源不同等原因,返回的JSON数据结构会出现不一致的情况,比如同一个字段在不同数据样本中可能是数组、对象或者null,此时直接对字段进行循环遍历就会抛出类型错误,导致程序运行中断。
常见的问题场景
JSON数据结构不一致导致遍历失败的场景主要有以下几种:
- 目标字段预期是数组,但实际返回的是单个对象,比如
list字段有时是[{id:1}]有时是{id:1} - 目标字段预期是数组,但实际返回的是null、undefined或者空字符串
- 数组内部元素的字段缺失,或者字段类型不统一,遍历时访问不存在的字段报错
- 接口返回的数据嵌套层级不一致,有的数据多嵌套一层,有的少嵌套一层
通用解决方案
1. 数据预处理统一结构
在遍历之前先对数据做预处理,把不一致的字段统一转换成预期的结构,这是最稳妥的方式。比如把非数组类型的字段统一转换成数组,给缺失的字段设置默认值。
// 原始接口返回数据,结构不一致
const rawDataList = [
{
id: 1,
tags: [{name: '前端'}, {name: 'JS'}] // 正常数组结构
},
{
id: 2,
tags: {name: '后端'} // 单个对象结构
},
{
id: 3,
tags: null // 空值结构
},
{
id: 4
// 缺失tags字段
}
];
// 预处理函数,统一tags字段为数组
function normalizeData(dataList) {
return dataList.map(item => {
let tags = item.tags;
// 如果tags是对象,转成数组
if (tags && typeof tags === 'object' && !Array.isArray(tags)) {
tags = [tags];
}
// 如果tags不是数组或者是空值,设置为空数组
if (!Array.isArray(tags)) {
tags = [];
}
return {
...item,
tags
};
});
}
const normalizedList = normalizeData(rawDataList);
console.log(normalizedList);
2. 遍历前做类型校验
如果不想做全局预处理,也可以在每次遍历前先校验目标字段的类型,确认是数组再进行遍历,避免直接调用数组的遍历方法报错。
// 遍历前校验类型
function traverseTags(dataItem) {
const tags = dataItem.tags;
// 先判断tags是否是数组,不是的话不执行遍历
if (Array.isArray(tags)) {
tags.forEach(tag => {
console.log('标签名称:', tag.name || '未知标签');
});
} else {
console.log('当前数据无有效标签');
}
}
// 测试不同结构的数据
traverseTags({id: 1, tags: [{name: '前端'}]}); // 正常遍历
traverseTags({id: 2, tags: {name: '后端'}}); // 输出无有效标签
traverseTags({id: 3}); // 输出无有效标签
3. 给嵌套字段设置默认值
如果数据结构存在嵌套层级不一致的问题,可以使用可选链和空值合并运算符给嵌套字段设置默认值,避免访问不存在的属性报错。
// 嵌套结构不一致的数据
const nestedDataList = [
{
user: {
info: {
hobbies: ['篮球', '足球']
}
}
},
{
user: {} // 缺失info字段
},
{
// 缺失user字段
}
];
// 遍历嵌套的hobbies字段
nestedDataList.forEach(item => {
// 使用可选链判断属性是否存在,不存在的话用空数组兜底
const hobbies = item?.user?.info?.hobbies ?? [];
hobbies.forEach(hobby => {
console.log('爱好:', hobby);
});
});
方案对比
不同的解决方案适用场景不同,以下是各方案的特点对比:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 数据预处理统一结构 | 后续所有逻辑不需要重复做校验,代码更简洁 | 需要额外写预处理逻辑,增加少量性能开销 | 数据结构不一致问题普遍存在,后续多处需要使用该数据 |
| 遍历前类型校验 | 逻辑灵活,不需要全局修改数据 | 每次遍历都要写校验逻辑,代码冗余度高 | 只有个别地方需要遍历该字段,不一致问题不普遍 |
| 可选链+默认值 | 语法简洁,不需要额外写函数 | 只能处理属性缺失问题,无法处理类型不匹配问题 | 只是嵌套层级不一致,字段类型统一的情况 |
注意事项
在处理JSON数据时还需要注意以下几点:
- 预处理逻辑要覆盖所有可能的异常结构,比如除了对象和数组,还要考虑字符串、数字等意外类型
- 如果数据量非常大,预处理会占用一定内存,可以根据实际情况选择是否要做深拷贝
- 对于接口返回的数据,最好和后端约定好统一的数据结构,从根源上减少不一致问题
- 可以在预处理时打印异常数据,方便后续排查数据来源的问题