JSON.stringify忽略数组自定义键,如何保留混合结构的键值对?
如何在JSON序列化时保留数组的自定义键值对?
这个问题我之前也踩过坑,核心原因是JSON的数组规范和JavaScript数组的特性不匹配:
JavaScript里的数组本质是特殊的对象,所以你可以给它添加任意非数字索引的属性;但JSON的数组类型只认可连续的数字索引(从0开始)对应的元素,所有非数字键的属性都会在JSON.stringify时被直接忽略,这就是你看到x、y丢失的原因。
下面给你几个可行的解决方案,按推荐程度排序:
1. 改用对象包裹结构(最推荐)
既然混合数组+自定义属性的结构不符合JSON规范,不如直接把数据拆分成“数组元素”和“自定义属性”两部分,放在一个普通对象里。这样结构清晰,序列化完全没问题,也符合所有JSON解析器的预期:
// 重构你的数据结构 const data = { items: [12, 15], // 原来的数组元素 x: 12, y: 15 }; // 序列化结果完整保留所有数据 JSON.stringify(data); // 输出:{"items":[12,15],"x":12,"y":15} // 访问方式也很直观 console.log(data.items[0]); // 12 console.log(data.x); // 12
2. 自定义JSON.stringify的replacer函数(保留原结构的折中方案)
如果你一定要维持原有的数组+自定义属性结构,可以通过自定义replacer函数,手动把数组的非数字属性合并到一个对象里再序列化。注意:这样序列化后的结果会是对象类型(因为JSON不支持带自定义属性的数组),之后解析时需要再转回去:
序列化时的replacer函数
function arrayWithPropsReplacer(key, value) { if (Array.isArray(value)) { const result = {}; // 先复制数组的数字索引元素 for (let i = 0; i < value.length; i++) { result[i] = value[i]; } // 再复制所有非数字的可枚举属性 for (const prop in value) { if (!/^\d+$/.test(prop)) { result[prop] = value[prop]; } } return result; } return value; } // 你的原数组 const arrObj = []; arrObj["x"] = 12; arrObj.push(12); arrObj["y"] = 15; arrObj.push(15); // 序列化后得到包含所有键的对象 const serialized = JSON.stringify(arrObj, arrayWithPropsReplacer); // 输出:{"0":12,"1":15,"x":12,"y":15}
解析时的reviver函数(还原回原结构)
如果需要把序列化后的字符串解析回原来的混合结构,可以搭配reviver函数:
function arrayWithPropsReviver(key, value) { if (typeof value === 'object' && value !== null) { const keys = Object.keys(value); // 检查是否有连续的数字索引(从0开始) const numericKeys = keys.filter(k => /^\d+$/.test(k)).map(Number); if (numericKeys.length > 0 && Math.min(...numericKeys) === 0 && Math.max(...numericKeys) === numericKeys.length - 1) { const arr = []; // 先添加数组元素 numericKeys.forEach(k => arr[k] = value[k]); // 再添加非数字属性 keys.forEach(k => { if (!/^\d+$/.test(k)) { arr[k] = value[k]; } }); return arr; } } return value; } // 解析回原结构 const parsedArr = JSON.parse(serialized, arrayWithPropsReviver); console.log(parsedArr[0]); // 12 console.log(parsedArr.x); // 12
3. 额外提醒
虽然JavaScript允许给数组加自定义属性,但这种写法并不推荐:
- 很多数组方法(比如
map、filter、forEach)只会处理数字索引的元素,容易造成逻辑混淆; - 这种结构不符合常规的代码规范,其他开发者维护时容易踩坑;
- 除了JSON序列化,其他工具/库也可能不识别这些自定义属性。
所以优先考虑第一种重构方案,代码会更健壮、易维护。
内容的提问来源于stack exchange,提问作者ConductedClever
相关产品推荐
相关产品推荐

