
1. 理解问题:扁平化数据与目标结构
在前端开发或数据处理中,我们经常会遇到需要将扁平化数组转换为具有层级关系的数据结构。假设我们有一个JSON数组,其中每个对象都包含一个metaData.level属性,表示其在层级结构中的深度:
const content = [
{ title: "Item 1", metaData: { "level": 1, "desc": "Some Desc 1", "displayOnOverview": true }},
{ title: "Item 2", metaData: { "level": 2, "desc": "Some Desc 2", "displayOnOverview": true }},
{ title: "Item 3", metaData: { "level": 2, "desc": "Some Desc 3", "displayOnOverview": false }},
{ title: "Item 4", metaData: { "level": 3, "desc": "Some Desc 4", "displayOnOverview": true }},
{ title: "Item 5", metaData: { "level": 1, "desc": "Some Desc 5", "displayOnOverview": true }}
];我们的目标是将其转换为一个嵌套的JSON结构,其中子项通过subNav属性连接,如下所示:
[
{
"title": "Item 1",
"metaData": {
"level": 1,
"desc": "Some Desc 1",
"displayOnOverview": true
},
"subNav": [
{
"title": "Item 2",
"metaData": {
"level": 2,
"desc": "Some Desc 2",
"displayOnOverview": true
}
},
{
"title": "Item 3",
"metaData": {
"level": 2,
"desc": "Some Desc 3",
"displayOnOverview": false
},
"subNav": [
{
"title": "Item 4",
"metaData": {
"level": 3,
"desc": "Some Desc 4",
"displayOnOverview": true
}
}
]
}
]
},
{
"title": "Item 5",
"metaData": {
"level": 1,
"desc": "Some Desc 5",
"displayOnOverview": true
}
}
]直接通过数组索引判断父子关系(如root[index - 1])在多级嵌套中是不可行的,因为它无法准确追踪任意深度的父节点。我们需要一种更灵活的机制来识别并连接父子节点。
2. 核心思路:父节点追踪与层级映射
解决这个问题的关键在于,在遍历扁平化数组时,我们需要能够快速找到当前项的直接父节点。由于层级关系由metaData.level定义,我们可以维护一个映射表来存储每个层级的“最新”父节点。
立即学习“Java免费学习笔记(深入)”;
具体步骤如下:
- 初始化结果数组:用于存放所有顶层(level: 1)的节点。
- 初始化层级映射表 (itemMap):这是一个对象,键是层级(例如1, 2, 3),值是该层级的最新节点对象。当处理一个level为N的项时,itemMap[N-1]将指向其父节点。
-
遍历扁平化数组:对每个元素执行以下操作:
- 创建新节点:复制当前项的title和metaData到新节点,以便后续添加subNav。
-
判断层级:
- 如果metaData.level为1,则该项是顶层节点,直接添加到结果数组中。
- 如果metaData.level大于1,则计算其父级parentLevel(即currentLevel - 1)。
- 从itemMap中获取parentLevel对应的父节点。
- 如果父节点尚未有subNav属性,则初始化一个空数组。
- 将当前新节点添加到父节点的subNav数组中。
- 更新层级映射表:将当前新节点存储到itemMap[metaData.level]中。这样做是为了确保后续更深层级的子节点能够找到正确的直接父节点。
3. 实现代码示例
以下是使用JavaScript实现上述逻辑的完整代码:
const content = [
{ title: "Item 1", metaData: { "level": 1, "desc": "Some Desc 1", "displayOnOverview": true }},
{ title: "Item 2", metaData: { "level": 2, "desc": "Some Desc 2", "displayOnOverview": true }},
{ title: "Item 3", metaData: { "level": 2, "desc": "Some Desc 3", "displayOnOverview": false }},
{ title: "Item 4", metaData: { "level": 3, "desc": "Some Desc 4", "displayOnOverview": true }},
{ title: "Item 5", metaData: { "level": 1, "desc": "Some Desc 5", "displayOnOverview": true }}
];
/**
* 将扁平化JSON数组转换为基于level属性的多级嵌套结构。
* @param {Array4. 代码解析
- topLevelItems = []: 这个数组将保存最终输出的根节点。所有level: 1的项都会直接添加到这里。
-
itemMap = {}: 这是算法的核心。它是一个哈希表(JavaScript对象),用于存储每个层级(level)的最后一个处理过的节点。
- 当处理一个level为N的节点时,如果它是level: 1,它会被添加到topLevelItems。
- 如果它是level: N > 1,它的父节点应该是level: N-1的某个节点。itemMap[N-1]会保存我们最近处理过的level: N-1的节点,这正是我们需要的父节点。
- 处理完当前节点后,无论其level是多少,我们都会用它来更新itemMap[metaData.level]。这意味着,如果后续出现与当前节点同级或更深级的节点,它们将能够正确地找到父节点。
- for (const item of data): 遍历输入的扁平化数组。
- const newItem = { title, metaData };: 创建一个新对象。这里直接复制了原始的title和metaData,确保desc和displayOnOverview等额外属性也得以保留。
- if (metaData.level === 1): 如果当前项是顶层节点,直接将其添加到topLevelItems。
-
else { ... }: 如果是子节点:
- parentLevel = metaData.level - 1;: 确定父节点的层级。
- parentItem = itemMap[parentLevel];: 从itemMap中获取对应的父节点。
- if (!parentItem.subNav) { parentItem.subNav = []; }: 如果父节点还没有subNav数组,就创建一个。
- parentItem.subNav.push(newItem);: 将当前节点添加到父节点的subNav中。
- itemMap[metaData.level] = newItem;: 关键步骤。将当前处理的newItem存入itemMap,以其metaData.level作为键。这样,如果后续有同级或更深层级的节点,它就能成为它们的父节点。
5. 注意事项与扩展
- 数据顺序:此方法假设输入数据是按层级顺序组织的,即一个父节点总是出现在其所有子节点之前。如果数据顺序混乱(例如,level: 2的节点出现在其level: 1的父节点之前),itemMap[parentLevel]可能无法找到正确的父节点,导致结构错误或警告。
- 错误处理:在parentItem = itemMap[parentLevel];之后,可以添加更健壮的错误检查,例如if (!parentItem) { throw new Error("Parent item not found for level " + metaData.level); },以处理数据结构不一致的情况。
- 性能:该算法对输入数组进行了一次遍历,时间复杂度为O(N),其中N是数组的长度,效率较高。
- 灵活性:如果嵌套属性名(如subNav)或层级属性名(如metaData.level)可能变化,可以将它们作为参数传递给函数,提高代码的通用性。
- 深拷贝:示例中newItem = { title, metaData }是浅拷贝了metaData对象。如果需要确保metaData在后续操作中不会影响原始item,可以使用JSON.parse(JSON.stringify(item))或structuredClone(item)进行深拷贝。
6. 总结
通过维护一个层级映射表,我们可以有效地将扁平化的JSON数组转换为任意深度的嵌套结构。这种方法既高效又灵活,是处理此类数据转换问题的常用模式。理解itemMap在追踪父节点和更新当前层级最新节点方面的作用,是掌握这一技巧的关键。










