我有一个复杂的json文件,我必须处理javascript使其分层,以便稍后构建树。
json的每个条目都有:
Id:唯一的Id,
parentId:父节点的id(如果节点是树的根,则为0)
Level:树的深度级别
json数据已经“有序”。我的意思是,一个条目在它上面有一个父节点或兄弟节点,在它下面有一个子节点或兄弟节点。
输入:
{
"People": [
{
"id": "12",
"parentId": "0",
"text": "Man",
"level": "1",
"children": null
},
{
"id": "6",
"parentId": "12",
"text": "Boy",
"level": "2",
"children": null
},
{
"id": "7",
"parentId": "12",
"text": "Other",
"level": "2",
"children": null
},
{
"id": "9",
"parentId": "0",
"text": "Woman",
"level": "1",
"children": null
},
{
"id": "11",
"parentId": "9",
"text": "Girl",
"level": "2",
"children": null
}
],
"Animals": [
{
"id": "5",
"parentId": "0",
"text": "Dog",
"level": "1",
"children": null
},
{
"id": "8",
"parentId": "5",
"text": "Puppy",
"level": "2",
"children": null
},
{
"id": "10",
"parentId": "13",
"text": "Cat",
"level": "1",
"children": null
},
{
"id": "14",
"parentId": "13",
"text": "Kitten",
"level": "2",
"children": null
},
]
}
预期产量:
{
"People": [
{
"id": "12",
"parentId": "0",
"text": "Man",
"level": "1",
"children": [
{
"id": "6",
"parentId": "12",
"text": "Boy",
"level": "2",
"children": null
},
{
"id": "7",
"parentId": "12",
"text": "Other",
"level": "2",
"children": null
}
]
},
{
"id": "9",
"parentId": "0",
"text": "Woman",
"level": "1",
"children":
{
"id": "11",
"parentId": "9",
"text": "Girl",
"level": "2",
"children": null
}
}
],
"Animals": [
{
"id": "5",
"parentId": "0",
"text": "Dog",
"level": "1",
"children":
{
"id": "8",
"parentId": "5",
"text": "Puppy",
"level": "2",
"children": null
}
},
{
"id": "10",
"parentId": "13",
"text": "Cat",
"level": "1",
"children":
{
"id": "14",
"parentId": "13",
"text": "Kitten",
"level": "2",
"children": null
}
}
]
}
将节点数组转换为树
ES6函数转换数组节点(由父ID相关)到树结构:
/**
* Convert nodes list related by parent ID - to tree.
* @syntax getTree(nodesArray [, rootID [, propertyName]])
*
* @param {Array} arr Array of nodes
* @param {integer} id Defaults to 0
* @param {string} p Property name. Defaults to "parent_id"
* @returns {Object} Nodes tree
*/
const getTree = (arr, p = "parent_id") => arr.reduce((o, n) => {
if (!o[n.id]) o[n.id] = {};
if (!o[n[p]]) o[n[p]] = {};
if (!o[n[p]].nodes) o[n[p]].nodes= [];
if (o[n.id].nodes) n.nodes= o[n.id].nodes;
o[n[p]].nodes.push(n);
o[n.id] = n;
return o;
}, {});
从节点树生成HTML列表
有了我们的树,这里有一个递归函数来构建UL > LI元素:
/**
* Convert Tree structure to UL>LI and append to Element
* @syntax getTree(treeArray [, TargetElement [, onLICreatedCallback ]])
*
* @param {Array} tree Tree array of nodes
* @param {Element} el HTMLElement to insert into
* @param {function} cb Callback function called on every LI creation
*/
const treeToHTML = (tree, el, cb) => el.append(tree.reduce((ul, n) => {
const li = document.createElement('li');
if (cb) cb.call(li, n);
if (n.nodes?.length) treeToHTML(n.nodes, li, cb);
ul.append(li);
return ul;
}, document.createElement('ul')));
演示时间
下面是一个使用上述两个函数的线性节点数组的例子:
const getTree = (arr, p = "parent_id") => arr.reduce((o, n) => {
if (!o[n.id]) o[n.id] = {};
if (!o[n[p]]) o[n[p]] = {};
if (!o[n[p]].nodes) o[n[p]].nodes = [];
if (o[n.id].nodes) n.nodes = o[n.id].nodes;
o[n[p]].nodes.push(n);
o[n.id] = n;
return o;
}, {});
const treeToHTML = (tree, el, cb) => el.append(tree.reduce((ul, n) => {
const li = document.createElement('li');
if (cb) cb.call(li, n);
if (n.nodes?.length) treeToHTML(n.nodes, li, cb);
ul.append(li);
return ul;
}, document.createElement('ul')));
// DEMO TIME:
const nodesList = [
{id: 10, parent_id: 4, text: "Item 10"}, // PS: Order does not matters
{id: 1, parent_id: 0, text: "Item 1"},
{id: 4, parent_id: 0, text: "Item 4"},
{id: 3, parent_id: 5, text: "Item 3"},
{id: 5, parent_id: 4, text: "Item 5"},
{id: 2, parent_id: 1, text: "Item 2"},
];
const myTree = getTree(nodesList)[0].nodes; // Get nodes of Root (0)
treeToHTML(myTree, document.querySelector("#tree"), function(node) {
this.textContent = `(${node.parent_id} ${node.id}) ${node.text}`;
this._node = node;
this.addEventListener('click', clickHandler);
});
function clickHandler(ev) {
if (ev.target !== this) return;
console.clear();
console.log(this._node.id);
};
<div id="tree"></div>
这是我在一个react项目中使用的
// ListToTree.js
import _filter from 'lodash/filter';
import _map from 'lodash/map';
export default (arr, parentIdKey) => _map(_filter(arr, ar => !ar[parentIdKey]), ar => ({
...ar,
children: _filter(arr, { [parentIdKey]: ar.id }),
}));
用法:
// somewhere.js
import ListToTree from '../Transforms/ListToTree';
const arr = [
{
"id":"Bci6XhCLZKPXZMUztm1R",
"name":"Sith"
},
{
"id":"C3D71CMmASiR6FfDPlEy",
"name":"Luke",
"parentCategoryId":"ltatOlEkHdVPf49ACCMc"
},
{
"id":"aS8Ag1BQqxkO6iWBFnsf",
"name":"Obi Wan",
"parentCategoryId":"ltatOlEkHdVPf49ACCMc"
},
{
"id":"ltatOlEkHdVPf49ACCMc",
"name":"Jedi"
},
{
"id":"pw3CNdNhnbuxhPar6nOP",
"name":"Palpatine",
"parentCategoryId":"Bci6XhCLZKPXZMUztm1R"
}
];
const response = ListToTree(arr, 'parentCategoryId');
输出:
[
{
"id":"Bci6XhCLZKPXZMUztm1R",
"name":"Sith",
"children":[
{
"id":"pw3CNdNhnbuxhPar6nOP",
"name":"Palpatine",
"parentCategoryId":"Bci6XhCLZKPXZMUztm1R"
}
]
},
{
"id":"ltatOlEkHdVPf49ACCMc",
"name":"Jedi",
"children":[
{
"id":"C3D71CMmASiR6FfDPlEy",
"name":"Luke",
"parentCategoryId":"ltatOlEkHdVPf49ACCMc"
},
{
"id":"aS8Ag1BQqxkO6iWBFnsf",
"name":"Obi Wan",
"parentCategoryId":"ltatOlEkHdVPf49ACCMc"
}
]
}
]```
我使用@FurkanO answer并创建了一个可以用于任何对象类型的泛型函数,我还用TypeScript写了这个函数,我更喜欢它,因为它有自动补全功能。
实现:
1. Javascript:
export const flatListToTree = (flatList, idPath, parentIdPath, childListPath, isParent) => {
const rootParents = [];
const map = {};
for (const item of flatList) {
if (!item[childListPath]) item[childListPath] = [];
map[item[idPath]] = item;
}
for (const item of flatList) {
const parentId = item[parentIdPath];
if (isParent(item)) {
rootParents.push(item);
} else {
const parentItem = map[parentId];
parentItem[childListPath].push(item);
}
}
return rootParents;
};
2. TypeScript:我假设“T”类型有一个属性的孩子列表,你可以改变“childListPath”是一个字符串而不是“keyof T”如果你有不同的用例。
export const flatListToTree = <T>(
flatList: T[],
idPath: keyof T,
parentIdPath: keyof T,
childListPath: keyof T,
isParent: (t: T) => boolean,
) => {
const rootParents: T[] = [];
const map: any = {};
for (const item of flatList) {
if (!(item as any)[childListPath]) (item as any)[childListPath] = [];
map[item[idPath]] = item;
}
for (const item of flatList) {
const parentId = item[parentIdPath];
if (isParent(item)) {
rootParents.push(item);
} else {
const parentItem = map[parentId];
parentItem[childListPath].push(item);
}
}
return rootParents;
};
使用方法:
const nodes = [
{ id: 2, pid: undefined, children: [] },
{ id: 3, pid: 2 },
{ id: 4, pid: 2 },
{ id: 5, pid: 4 },
{ id: 6, pid: 5 },
{ id: 7, pid: undefined },
{ id: 8, pid: 7 },
];
const result = flatListToTree(nodes, "id", "pid", "children", node => node.pid === undefined);