const buildJsonWithFlatList = (flatList) => {
const json = {};
flatList?.forEach(({ key }) => {
const paths = key.split('&&');
fillJSONWithPropertyPaths(paths, json);
});
return json;
};
const fillJSONWithPropertyPaths = (propertyPaths, json) => {
let obj = json;
while (propertyPaths?.length) {
const property = propertyPaths.shift();
if (!obj['children']) {
obj = obj[property] = { children: {} };
continue;
}
if (obj['children'][property]) {
obj = obj['children'][property];
} else {
obj = obj['children'][property] = { children: {} };
}
}
};
输入:['a', 'b', 'c']
输出
{
a: {
children: {
b: {
children: {
c: { children: {} }
}
}
}
}
}
输入
const flatList = [
{ key: 'a&&b&&c' },
{ key: 'a&&b&&d' },
{ key: 'a&&e' }
];
输出
{
a: {
children: {
b: {
children: {
c: { children: {} },
d: { children: {} }
}
},
e: {
children: {}
}
}
}
}
这里有一段代码
fillJSONWithPropertyPaths 函数中始终操作的是同一个 json 对象 所以结果只会保留最后一个结果
修改建议
1. 修改 fillJSONWithPropertyPaths 函数
创建一个全新的对象来存储当前路径的结果。
将新对象合并到最终的 JSON 结构中。3. 新增 mergeJson 函数
用于递归合并两个 JSON 对象。
const fillJSONWithPropertyPaths = (propertyPaths) => { const result = {}; let obj = result; while (propertyPaths?.length) { const property = propertyPaths.shift(); obj[property] = { children: {} }; obj = obj[property].children; } return result; };
const buildJsonWithFlatList = (flatList) => { const json = {}; flatList?.forEach(({ key }) => { const paths = key.split('&&'); const subJson = fillJSONWithPropertyPaths(paths); // 合并 subJson 到最终的 json 中 mergeJson(json, subJson); }); return json; };
const mergeJson = (target, source) => { for (const key in source) { if (!target[key]) { target[key] = source[key]; } else { mergeJson(target[key].children, source[key].children); } } };
3. 新增 mergeJson 函数
用于递归合并两个 JSON 对象。
fillJSONWithPropertyPaths
这里有一个思考
const fillJSONWithPropertyPaths = (propertyPaths) => {
const result = {};
let obj = result;
while (propertyPaths?.length) {
const property = propertyPaths.shift();
if (!obj[property]) {
obj[property] = { children: {} };
}
obj = obj[property].children;
}
return result;
};
这里 方法独立出一个 let obj = result; 而不是直接修改result的值
我们知道 对象之间的赋值是存在浅拷贝的 直接修改副本 原本也会改变 那为什么还要这么做呢
是为了在不破坏对象引用的前提下,逐层深入嵌套结构
我们来看一个反例:直接操作 result
const fillJSONWithPropertyPaths = (propertyPaths) => {
let result = {};
while (propertyPaths?.length) {
const property = propertyPaths.shift();
if (!result[property]) {
result[property] = { children: {} };
}
result = result[property].children; // ❌ 问题在这里
}
return result;
};
❌ 问题分析
初始时 result 是 {}。
第一次循环后,result[property] = { children: {} },然后你把 result = result[property].children。
这意味着你完全丢弃了原来的 result 引用,现在 result 指向的是 children 对象。
最终返回的是最内层的 children,而不是整个树结构。
所以最终返回的只是一个空对象 {},而不是完整的嵌套结构
使用 let obj = result 的好处
let obj = result;
while (...) {
...
obj = obj[property].children; // 修改的是 obj,不是 result
}obj 是对 result 的引用副本。
在循环中不断更新 obj,可以逐层深入嵌套结构。
原始的 result 始终保留着整个树的根节点引用,最后可以正确返回完整结构。
🧠 类比理解
你可以把它想象成“指针”操作:
result 是根目录。
obj 是一个游标(cursor),用来一层一层地往下走。
如果你把 result 当游标用了,你就找不到根目录了。
所以,引入 obj 是为了保持对原始对象的引用,同时又能灵活地进行嵌套操作。这是构建树形结构的一种常见技巧。
继续深入
第一步:初始化
const result = {};
let obj = result;result ──┐
▼
{ } ← obj第二步:进入循环
检查 obj['a'] 是否存在? ❌ 不存在
创建一个新的节点:obj['a'] = { children: {} };
更新
obj = obj['a'].children;
result ──┐
▼
{ a: { children: {} } } ← obj 现在指向 children🔄 第二次循环
检查 obj['b'] 是否存在? ❌ 不存在
创建新节点:obj['b'] = { children: {} };
obj = obj['b'].children;
result ──┐
▼
{ a: { children: { b: { children: {} } } } } ← obj 现在指向 b.children
第三步:退出循环,返回结果{
a: {
children: {
b: {
children: {
c: { children: {} }
}
}
}
}
}
✅ 结论
obj['a'] = { children: {} } 会直接修改 obj 的内容。
因为 obj 和 result 指向同一个对象,所以 result 也被修改了。
后续的 obj = obj['a'].children 是为了继续构建下一层结构,但不会影响 result 的结构。