如何将带嵌套扁平键的JavaScript对象转为嵌套JSON对象
问题:将带斜杠分隔嵌套键的扁平JavaScript对象解析为嵌套JSON对象
需求说明
需要把使用斜杠(/)分隔嵌套层级的扁平对象,转换为包含多层数组和对象的嵌套结构,同时要支持非连续数组索引(比如0、1、10这类不连续的索引值),实现通用的递归逻辑适配各种嵌套场景。
待解析的扁平对象
const object = { "item1": "value", "item2/0/subitem1": "value", "item2/0/subitem2": "value", "item2/1/subitem1": "value", "item2/1/subitem2": "value", "item3/0/subitem1/subsubitem1": "value", "item3/1/subitem1/subsubitem1": "value", "item4/0/subitem1/0/subsubitem1": "value", "item4/0/subitem1/1/subsubitem1": "value", }
期望转换后的嵌套对象
const parsedObject = { item1: "value", item2: [ { subitem1: "value", subitem2: "value", }, { subitem1: "value", subitem2: "value", } ], item3: [ { subitem1: { subsubitem1: "value", } }, { subitem1: { subsubitem1: "value", } }, ], item4: [ { subitem1: [ { subsubitem1: "value", }, { subsubitem1: "value", }, ] } ] }
当前瓶颈与尝试的代码
已编写处理函数,但无法支持非连续数组索引,需要优化递归逻辑以适配多层嵌套数组与对象的通用场景。尝试的代码如下:
const object = { "item1": "value", "item2/0/subitem1": "value", "item2/0/subitem2": "value", "item2/1/subitem1": "value", "item2/1/subitem2": "value", "item3/0/subitem1/subsubitem1": "value", "item3/1/subitem1/subsubitem1": "value", "item4/0/subitem1/0/subsubitem1": "value", "item4/0/subitem1/1/subsubitem1": "value", } const mainFunction = () => { const specie = {}; const map = new Map(); Object.keys(object).forEach((key) => { const keyTokens = key.split('/'); processKeyValues(specie, keyTokens, object[key], map); }); console.log(specie); } const convertStringToBoolean = (str) => { return str.toLowerCase() === 'true' ? true : str.toLowerCase() === 'false' ? false : str; }; const processKeyValues = (specie, keyTokens, value, map) => { keyTokens.reduce((prev, curr, index, array) => { if (index === array.length - 1) { const valuePossibleNumber = parseInt(value); //transform number strings into numbers if (!isNaN(valuePossibleNumber)) { prev[curr] = valuePossibleNumber; return; } prev[curr] = convertStringToBoolean(value); //boolean strings into booleans return; } const currentKeyPossibleNumber = parseInt(curr); const currentKeyIsNumber = !isNaN(currentKeyPossibleNumber); const nextKeyPossibleNumber = parseInt(array[index + 1]); const nextKeyIsNumber = !isNaN(nextKeyPossibleNumber); if (currentKeyIsNumber) { const hasMappedIndex = map.has(currentKeyPossibleNumber); let mappedIndex = map.get(currentKeyPossibleNumber); if (!hasMappedIndex) { mappedIndex = map.size; map.set(currentKeyPossibleNumber, mappedIndex); } const currentKeyAsIndexDoesNotExist = !prev[mappedIndex]; if (currentKeyAsIndexDoesNotExist) { if (nextKeyIsNumber) { prev.push([]); } else { prev.push({}); } } return prev[mappedIndex]; } const currentKeyDoesNotExist = !prev[curr]; if (currentKeyDoesNotExist) { if (nextKeyIsNumber) { prev[curr] = []; map.clear(); } else { prev[curr] = {}; } } return prev[curr]; }, specie); };
解决方案
核心思路
- 遍历每个扁平键,按斜杠分割为层级路径数组
- 迭代处理路径,判断每个路径段是否为数组索引(纯数字字符串)
- 对于数组,直接使用原始索引值,不存在的索引位置保留默认状态(可按需调整补空逻辑)
- 根据下一个路径段的类型,自动初始化当前层级为数组或对象
优化后的代码
const flattenToNested = (flatObj) => { const result = {}; // 处理值的类型转换(数字、布尔值) const parseValue = (val) => { if (!isNaN(val) && !isNaN(parseFloat(val))) { return Number(val); } if (val.toLowerCase() === 'true') return true; if (val.toLowerCase() === 'false') return false; return val; }; // 处理单个键值对的嵌套逻辑 const processEntry = (key, value) => { const pathSegments = key.split('/'); let current = result; for (let i = 0; i < pathSegments.length; i++) { const segment = pathSegments[i]; const isLastSegment = i === pathSegments.length - 1; // 判断当前段是否为数组索引(纯数字格式) const isIndex = /^\d+$/.test(segment); const index = Number(segment); if (isLastSegment) { // 最后一段,直接赋值 if (isIndex) { if (!Array.isArray(current)) { throw new Error(`路径${pathSegments.slice(0, i).join('/')}不是数组,无法赋值索引`); } current[index] = parseValue(value); } else { current[segment] = parseValue(value); } return; } // 非最后一段,构建嵌套结构 if (isIndex) { if (!Array.isArray(current)) { throw new Error(`路径${pathSegments.slice(0, i).join('/')}不是数组,无法创建索引`); } // 索引位置不存在时,根据下一段类型初始化结构 if (!current[index]) { const nextIsIndex = /^\d+$/.test(pathSegments[i+1]); current[index] = nextIsIndex ? [] : {}; } current = current[index]; } else { // 对象键不存在时,根据下一段类型初始化结构 if (!current[segment]) { const nextIsIndex = /^\d+$/.test(pathSegments[i+1]); current[segment] = nextIsIndex ? [] : {}; } current = current[segment]; } } }; // 遍历所有扁平键值对处理 Object.entries(flatObj).forEach(([key, value]) => { processEntry(key, value); }); return result; }; // 测试示例(包含非连续索引场景) const object = { "item1": "value", "item2/0/subitem1": "value", "item2/0/subitem2": "value", "item2/1/subitem1": "value", "item2/1/subitem2": "value", "item3/0/subitem1/subsubitem1": "value", "item3/1/subitem1/subsubitem1": "value", "item4/0/subitem1/0/subsubitem1": "value", "item4/0/subitem1/1/subsubitem1": "value", "item5/2/subitem": "non-consecutive", "item5/0/subitem": "first" }; console.log(JSON.stringify(flattenToNested(object), null, 2));
代码说明
- 类型转换:保留原逻辑,自动将数字字符串、布尔值字符串转为对应类型
- 非连续索引支持:直接使用原始数字索引,数组中未赋值的索引位置会保留为
undefined,可根据需求添加补空逻辑 - 结构自动判断:根据下一个路径段是否为数字,自动初始化当前层级为数组或对象
- 错误处理:添加基础的类型校验,避免非法结构赋值
内容的提问来源于stack exchange,提问作者Florin200217
相关产品推荐
相关产品推荐

