我有一个复杂的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
}
}
]
}
答案 0 :(得分:105)
如果使用地图查找,则有一种有效的解决方案。如果父母总是来到他们的孩子面前,你可以合并两个for-loops。它支持多个根。它在悬空分支上给出错误,但可以修改为忽略它们。它不需要第三方库。据我所知,这是最快的解决方案。
function list_to_tree(list) {
var map = {}, node, roots = [], i;
for (i = 0; i < list.length; i += 1) {
map[list[i].id] = i; // initialize the map
list[i].children = []; // initialize the children
}
for (i = 0; i < list.length; i += 1) {
node = list[i];
if (node.parentId !== "0") {
// if you have dangling branches check that map[node.parentId] exists
list[map[node.parentId]].children.push(node);
} else {
roots.push(node);
}
}
return roots;
}
var entries = [
{
"id": "12",
"parentId": "0",
"text": "Man",
"level": "1"
}, { /*...*/ }
];
console.log(list_to_tree(entries));
如果你进入复杂性理论,这个解决方案就是Θ(n log(n))。递归滤波器解决方案是Θ(n ^ 2),这可能是大数据集的问题。
答案 1 :(得分:67)
正如@Sander所提到的,@Halcyon`s answer假定一个预先排序的数组,以下不是。 (但它确实假设你已经加载了underscore.js - 虽然它可以用vanilla javascript编写):
unflatten = function( array, parent, tree ){
tree = typeof tree !== 'undefined' ? tree : [];
parent = typeof parent !== 'undefined' ? parent : { id: 0 };
var children = _.filter( array, function(child){ return child.parentid == parent.id; });
if( !_.isEmpty( children ) ){
if( parent.id == 0 ){
tree = children;
}else{
parent['children'] = children;
}
_.each( children, function( child ){ unflatten( array, child ) } );
}
return tree;
}
它假定属性'id'和'parentid'分别表示ID和父ID。必须有父ID为0的元素,否则返回一个空数组。孤儿和他们的后代都“迷失”
//Array to convert to tree structure.
var arr = [
{'id':1 ,'parentid' : 0},
{'id':2 ,'parentid' : 1},
{'id':3 ,'parentid' : 1},
{'id':4 ,'parentid' : 2},
{'id':5 ,'parentid' : 0},
{'id':6 ,'parentid' : 0},
{'id':7 ,'parentid' : 4}
];
tree = unflatten( arr );
答案 2 :(得分:25)
有同样的问题,但我无法确定数据是已排序还是。我不能使用第三方库,所以这只是香草Js;输入数据可以从@ Stephen的例子中获取;
function unflatten(arr) {
var tree = [],
mappedArr = {},
arrElem,
mappedElem;
// First map the nodes of the array to an object -> create a hash table.
for(var i = 0, len = arr.length; i < len; i++) {
arrElem = arr[i];
mappedArr[arrElem.id] = arrElem;
mappedArr[arrElem.id]['children'] = [];
}
for (var id in mappedArr) {
if (mappedArr.hasOwnProperty(id)) {
mappedElem = mappedArr[id];
// If the element is not at the root level, add it to its parent array of children.
if (mappedElem.parentid) {
mappedArr[mappedElem['parentid']]['children'].push(mappedElem);
}
// If the element is at the root level, add it to first level elements array.
else {
tree.push(mappedElem);
}
}
}
return tree;
}
JS Fiddle
答案 3 :(得分:14)
一个更简单的函数list-to-tree-lite
npm install list-to-tree-lite
listToTree(list)
来源:
function listToTree(data, options) {
options = options || {};
var ID_KEY = options.idKey || 'id';
var PARENT_KEY = options.parentKey || 'parent';
var CHILDREN_KEY = options.childrenKey || 'children';
var tree = [],
childrenOf = {};
var item, id, parentId;
for (var i = 0, length = data.length; i < length; i++) {
item = data[i];
id = item[ID_KEY];
parentId = item[PARENT_KEY] || 0;
// every item may have children
childrenOf[id] = childrenOf[id] || [];
// init its children
item[CHILDREN_KEY] = childrenOf[id];
if (parentId != 0) {
// init its parent's children object
childrenOf[parentId] = childrenOf[parentId] || [];
// push it into its parent's children object
childrenOf[parentId].push(item);
} else {
tree.push(item);
}
};
return tree;
}
答案 4 :(得分:11)
非常直接的方法是
(BONUS1:NODES可能会或可能不会被订购)
(BONUS2:不需要第三方图书馆,PLAIN JS)
const createDataTree = dataset => {
let hashTable = Object.create(null)
dataset.forEach( aData => hashTable[aData.ID] = { ...aData, childNodes : [] } )
let dataTree = []
dataset.forEach( aData => {
if( aData.parentID ) hashTable[aData.parentID].childNodes.push(hashTable[aData.ID])
else dataTree.push(hashTable[aData.ID])
} )
return dataTree
}
以下是对它的测试,可能有所帮助:
it('creates a correct shape of dataTree', () => {
let dataSet = [
{
"ID": 1,
"Phone": "(403) 125-2552",
"City": "Coevorden",
"Name": "Grady"
},
{
"ID": 2,
"parentID": 1,
"Phone": "(979) 486-1932",
"City": "Chełm",
"Name": "Scarlet"
}
]
let expectedDataTree = [
{
"ID": 1,
"Phone": "(403) 125-2552",
"City": "Coevorden",
"Name": "Grady",
childNodes : [
{
"ID": 2,
"parentID": 1,
"Phone": "(979) 486-1932",
"City": "Chełm",
"Name": "Scarlet",
childNodes : []
}
]
}
]
expect( createDataTree(dataSet) ).toEqual(expectedDataTree)
});
答案 5 :(得分:9)
您只需两行编码即可处理此问题:
_(flatArray).forEach(f=>
{f.nodes=_(flatArray).filter(g=>g.parentId==f.id).value();});
var resultArray=_(flatArray).filter(f=>f.parentId==null).value();
Test Online(请参阅浏览器控制台了解已创建的树)
的要求:
1-安装lodash 4(一个Javascript库,用于使用高性能方法操作对象和集合=&gt;像c#中的Linq一样)Lodash
2-如下所示的flatArray:
var flatArray=
[{
id:1,parentId:null,text:"parent1",nodes:[]
}
,{
id:2,parentId:null,text:"parent2",nodes:[]
}
,
{
id:3,parentId:1,text:"childId3Parent1",nodes:[]
}
,
{
id:4,parentId:1,text:"childId4Parent1",nodes:[]
}
,
{
id:5,parentId:2,text:"childId5Parent2",nodes:[]
}
,
{
id:6,parentId:2,text:"childId6Parent2",nodes:[]
}
,
{
id:7,parentId:3,text:"childId7Parent3",nodes:[]
}
,
{
id:8,parentId:5,text:"childId8Parent5",nodes:[]
}];
感谢Bakhshabadi先生
祝你好运答案 6 :(得分:7)
包list-to-tree可能很有用 安装:
bower install list-to-tree --save
或
npm install list-to-tree --save
例如,有列表:
var list = [
{
id: 1,
parent: 0
}, {
id: 2,
parent: 1
}, {
id: 3,
parent: 1
}, {
id: 4,
parent: 2
}, {
id: 5,
parent: 2
}, {
id: 6,
parent: 0
}, {
id: 7,
parent: 0
}, {
id: 8,
parent: 7
}, {
id: 9,
parent: 8
}, {
id: 10,
parent: 0
}
];
使用包列表到树:
var ltt = new LTT(list, {
key_id: 'id',
key_parent: 'parent'
});
var tree = ltt.GetTree();
结果:
[{
"id": 1,
"parent": 0,
"child": [
{
"id": 2,
"parent": 1,
"child": [
{
"id": 4,
"parent": 2
}, {
"id": 5, "parent": 2
}
]
},
{
"id": 3,
"parent": 1
}
]
}, {
"id": 6,
"parent": 0
}, {
"id": 7,
"parent": 0,
"child": [
{
"id": 8,
"parent": 7,
"child": [
{
"id": 9,
"parent": 8
}
]
}
]
}, {
"id": 10,
"parent": 0
}];
答案 7 :(得分:2)
这是针对无序商品的提案。此函数使用单个循环和哈希表,并使用id
收集所有项目。如果找到根节点,则将该对象添加到结果数组中。
function getTree(data, root) {
var o = {};
data.forEach(function (a) {
if (o[a.id] && o[a.id].children) {
a.children = o[a.id].children;
}
o[a.id] = a;
o[a.parentId] = o[a.parentId] || {};
o[a.parentId].children = o[a.parentId].children || [];
o[a.parentId].children.push(a);
});
return o[root].children;
}
var data = { 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 }] },
tree = Object.keys(data).reduce(function (r, k) {
r[k] = getTree(data[k], '0');
return r;
}, {});
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
答案 8 :(得分:1)
几天前我不得不从平面数组显示文件夹树时遇到了类似的问题。我在这里没有在TypeScript中看到任何解决方案,因此希望对您有所帮助。
在我的情况下,主要父对象只有一个,也不必对rawData数组进行排序。基于像这样准备临时对象的解决方案
const flatData: any[] = Folder.ofCollection([
{id: '1', title: 'some title' },
{id: '2', title: 'some title', parentId: 1 },
{id: '3', title: 'some title', parentId: 7 },
{id: '4', title: 'some title', parentId: 1 },
{id: '5', title: 'some title', parentId: 2 },
{id: '6', title: 'some title', parentId: 5 },
{id: '7', title: 'some title', parentId: 5 },
]);
原始数据示例
export default class Folder {
public static of(data: any): Folder {
return new Folder(data);
}
public static ofCollection(objects: any[] = []): Folder[] {
return objects.map((obj) => new Folder(obj));
}
public id: string;
public parentId: string | null;
public title: string;
public children: Folder[];
constructor(data: any = {}) {
this.id = data.id;
this.parentId = data.parentId || null;
this.title = data.title;
this.children = data.children || [];
}
}
文件夹
的定义 public getTree(flatData: any[]): Folder[] {
const addChildren = (item: Folder) => {
item.children = tempChild[item.id] || [];
if (item.children.length) {
item.children.forEach((child: Folder) => {
addChildren(child);
});
}
};
const tempChild: any = {};
flatData.forEach((item: Folder) => {
const parentId = item.parentId || 0;
Array.isArray(tempChild[parentId]) ? tempChild[parentId].push(item) : (tempChild[parentId] = [item]);
});
const tree: Folder[] = tempChild[0];
tree.forEach((base: Folder) => {
addChildren(base);
});
return tree;
}
解决方案:该函数返回平面参数的树结构
# shellcheck source=./lib.sh
source "$(find_install_dir)/lib.sh"
答案 9 :(得分:1)
使用此ES6方法。像魅力一样工作
// Data Set
// One top level comment
var comments = [{
id: 1,
parent_id: null
}, {
id: 2,
parent_id: 1
}, {
id: 3,
parent_id: 1
}, {
id: 4,
parent_id: 2
}, {
id: 5,
parent_id: 4
}];
const nest = (items, id = null, link = 'parent_id') =>
items
.filter(item => item[link] === id)
.map(item => ({ ...item, children: nest(items, item.id) }));
nest(comments);
答案 10 :(得分:1)
您可能会看到npm软件包tree-util 如果要从SQL DB数据表中加载数据,也可以非常方便。 您还可以轻松地将额外的数据添加到创建的树中的节点上。
免责声明,我做了这个包裹。
答案 11 :(得分:1)
基于@FurkanO's answer,我创建了另一个不会改变原始数据的版本(例如请求的@ Dac0d3r)。我非常喜欢@shekhardtu's answer,但意识到它必须多次过滤数据。我认为解决方案可能是先复制数据,然后使用FurkanO的答案。我在jsperf中尝试了我的版本,但不幸的是(非常)令人沮丧。结果似乎被接受的答案确实是一个很好的答案!我的版本是相当可配置的,并且具有故障保护功能,因此无论如何我都与你们共享。这是我的贡献:
function unflat(data, options = {}) {
const { id, parentId, childrenKey } = {
id: "id",
parentId: "parentId",
childrenKey: "children",
...options
};
const copiesById = data.reduce(
(copies, datum) => ((copies[datum[id]] = datum) && copies),
{}
);
return Object.values(copiesById).reduce(
(root, datum) => {
if ( datum[parentId] && copiesById[datum[parentId]] ) {
copiesById[datum[parentId]][childrenKey] = [ ...copiesById[datum[parentId]][childrenKey], datum ];
} else {
root = [ ...root, datum ];
}
return root
}, []
);
}
const data = [
{
"account": "10",
"name": "Konto 10",
"parentAccount": null
},{
"account": "1010",
"name": "Konto 1010",
"parentAccount": "10"
},{
"account": "10101",
"name": "Konto 10101",
"parentAccount": "1010"
},{
"account": "10102",
"name": "Konto 10102",
"parentAccount": "1010"
},{
"account": "10103",
"name": "Konto 10103",
"parentAccount": "1010"
},{
"account": "20",
"name": "Konto 20",
"parentAccount": null
},{
"account": "2020",
"name": "Konto 2020",
"parentAccount": "20"
},{
"account": "20201",
"name": "Konto 20201",
"parentAccount": "2020"
},{
"account": "20202",
"name": "Konto 20202",
"parentAccount": "2020"
}
];
const options = {
id: "account",
parentId: "parentAccount",
childrenKey: "children"
};
console.log(
"Hierarchical tree",
unflat(data, options)
);
使用options参数,可以配置要用作id或父id的属性。如果有人想要"childNodes": []
之类的东西,也可以配置children属性的名称。
OP可以简单地使用默认选项:
input.People = unflat(input.People);
如果父ID为假(null
,undefined
或其他假值),或者父对象不存在,则我们将该对象视为根节点。
答案 12 :(得分:1)
var data = [{"country":"india","gender":"male","type":"lower","class":"X"},
{"country":"china","gender":"female","type":"upper"},
{"country":"india","gender":"female","type":"lower"},
{"country":"india","gender":"female","type":"upper"}];
var seq = ["country","type","gender","class"];
var treeData = createHieArr(data,seq);
console.log(treeData)
function createHieArr(data,seq){
var hieObj = createHieobj(data,seq,0),
hieArr = convertToHieArr(hieObj,"Top Level");
return [{"name": "Top Level", "parent": "null",
"children" : hieArr}]
function convertToHieArr(eachObj,parent){
var arr = [];
for(var i in eachObj){
arr.push({"name":i,"parent":parent,"children":convertToHieArr(eachObj[i],i)})
}
return arr;
}
function createHieobj(data,seq,ind){
var s = seq[ind];
if(s == undefined){
return [];
}
var childObj = {};
for(var ele of data){
if(ele[s] != undefined){
if(childObj[ele[s]] == undefined){
childObj[ele[s]] = [];
}
childObj[ele[s]].push(ele);
}
}
ind = ind+1;
for(var ch in childObj){
childObj[ch] = createHieobj(childObj[ch],seq,ind)
}
return childObj;
}
}
&#13;
答案 13 :(得分:1)
我喜欢@WilliamLeung的纯JavaScript解决方案,但有时您需要在现有数组中进行更改以保留对象的引用。
function listToTree(data, options) {
options = options || {};
var ID_KEY = options.idKey || 'id';
var PARENT_KEY = options.parentKey || 'parent';
var CHILDREN_KEY = options.childrenKey || 'children';
var item, id, parentId;
var map = {};
for(var i = 0; i < data.length; i++ ) { // make cache
if(data[i][ID_KEY]){
map[data[i][ID_KEY]] = data[i];
data[i][CHILDREN_KEY] = [];
}
}
for (var i = 0; i < data.length; i++) {
if(data[i][PARENT_KEY]) { // is a child
if(map[data[i][PARENT_KEY]]) // for dirty data
{
map[data[i][PARENT_KEY]][CHILDREN_KEY].push(data[i]); // add child to parent
data.splice( i, 1 ); // remove from root
i--; // iterator correction
} else {
data[i][PARENT_KEY] = 0; // clean dirty data
}
}
};
return data;
}
Exapmle: https://jsfiddle.net/kqw1qsf0/17/
答案 14 :(得分:1)
也可以使用lodashjs(v4.x)
function buildTree(arr){
var a=_.keyBy(arr, 'id')
return _
.chain(arr)
.groupBy('parentId')
.forEach(function(v,k){
k!='0' && (a[k].children=(a[k].children||[]).concat(v));
})
.result('0')
.value();
}
答案 15 :(得分:1)
这是一个简单的辅助函数,我根据以上答案建立了模型,为Babel环境量身定制:
import { isEmpty } from 'lodash'
export default function unflattenEntities(entities, parent = {id: null}, tree = []) {
let children = entities.filter( entity => entity.parent_id == parent.id)
if (!isEmpty( children )) {
if ( parent.id == null ) {
tree = children
} else {
parent['children'] = children
}
children.map( child => unflattenEntities( entities, child ) )
}
return tree
}
答案 16 :(得分:0)
我的解决方案:
/**
*
* @param data items array
* @param idKey item's id key (e.g., item.id)
* @param parentIdKey item's key that points to parent (e.g., item.parentId)
* @param noParentValue item's parent value when root (e.g., item.parentId === noParentValue => item is root)
* @param bidirectional should parent reference be added
*/
function flatToTree(data, idKey, parentIdKey, noParentValue = null, bidirectional = true) {
const nodes = {}, roots = {}, leaves = {};
// iterate over all data items
for (const i of data) {
// add item as a node and possibly as a leaf
if (nodes[i[idKey]]) { // already seen this item when child was found first
// add all of the item's data and found children
nodes[i[idKey]] = Object.assign(nodes[i[idKey]], i);
} else { // never seen this item
// add to the nodes map
nodes[i[idKey]] = Object.assign({ $children: []}, i);
// assume it's a leaf for now
leaves[i[idKey]] = nodes[i[idKey]];
}
// put the item as a child in parent item and possibly as a root
if (i[parentIdKey] !== noParentValue) { // item has a parent
if (nodes[i[parentIdKey]]) { // parent already exist as a node
// add as a child
(nodes[i[parentIdKey]].$children || []).push( nodes[i[idKey]] );
} else { // parent wasn't seen yet
// add a "dummy" parent to the nodes map and put the item as its child
nodes[i[parentIdKey]] = { $children: [ nodes[i[idKey]] ] };
}
if (bidirectional) {
// link to the parent
nodes[i[idKey]].$parent = nodes[i[parentIdKey]];
}
// item is definitely not a leaf
delete leaves[i[parentIdKey]];
} else { // this is a root item
roots[i[idKey]] = nodes[i[idKey]];
}
}
return {roots, nodes, leaves};
}
用法示例:
const data = [{id: 2, parentId: 0}, {id: 1, parentId: 2} /*, ... */];
const { nodes, roots, leaves } = flatToTree(data, 'id', 'parentId', 0);
答案 17 :(得分:0)
ES6地图版本:
getTreeData = (items) => {
if (items && items.length > 0) {
const data = [];
const map = {};
items.map((item) => {
const id = item.id; // custom id selector !!!
if (!map.hasOwnProperty(id)) {
// in case of duplicates
map[id] = {
...item,
children: [],
};
}
});
for (const id in map) {
if (map.hasOwnProperty(id)) {
let mappedElem = [];
mappedElem = map[id];
/// parentId : use custom id selector for parent
if (
mappedElem.parentId &&
typeof map[mappedElem.parentId] !== "undefined"
) {
map[mappedElem.parentId].children.push(mappedElem);
} else {
data.push(mappedElem);
}
}
}
return data;
}
return [];
};
/// use like this :
const treeData = getTreeData(flatList);
答案 18 :(得分:0)
回答类似的问题:
https://stackoverflow.com/a/61575152/7388356
您可以使用ES6中引入的Map
对象。基本上,与其通过再次遍历数组来查找父项,还不如通过父项的ID从数组中获取父项,就像通过索引获取数组中的项一样。
这是简单的示例:
const 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
}
];
function toTree(arr) {
let arrMap = new Map(arr.map(item => [item.id, item]));
let tree = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (item.parentId !== "0") {
let parentItem = arrMap.get(item.parentId);
if (parentItem) {
let { children } = parentItem;
if (children) {
parentItem.children.push(item);
} else {
parentItem.children = [item];
}
}
} else {
tree.push(item);
}
}
return tree;
}
let tree = toTree(people);
console.log(tree);
答案 19 :(得分:0)
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;
}, {});
将我们的树放置在适当的位置,这是一个递归函数,用于构建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>
答案 20 :(得分:0)
我写了一个基于@Halcyon答案的ES6版本
On
此算法的原理是使用“映射”建立索引关系。可以很容易地通过“ parentId”在列表中找到“ item”,并在每个“ item”中添加“ children”,因为“ list”是引用关系,因此“ roots”将与整个树建立关系。>
答案 21 :(得分:0)
我的打字稿解决方案,也许可以帮助您:
type ITreeItem<T> = T & {
children: ITreeItem<T>[],
};
type IItemKey = string | number;
function createTree<T>(
flatList: T[],
idKey: IItemKey,
parentKey: IItemKey,
): ITreeItem<T>[] {
const tree: ITreeItem<T>[] = [];
// hash table.
const mappedArr = {};
flatList.forEach(el => {
const elId: IItemKey = el[idKey];
mappedArr[elId] = el;
mappedArr[elId].children = [];
});
// also you can use Object.values(mappedArr).forEach(...
// but if you have element which was nested more than one time
// you should iterate flatList again:
flatList.forEach((elem: ITreeItem<T>) => {
const mappedElem = mappedArr[elem[idKey]];
if (elem[parentKey]) {
mappedArr[elem[parentKey]].children.push(elem);
} else {
tree.push(mappedElem);
}
});
return tree;
}
用法示例:
createTree(yourListData, 'id', 'parentId');
答案 22 :(得分:0)
Incase 任何人需要它为多个父母。引用 id 2 有多个父节点
const dataSet = [{
"ID": 1,
"Phone": "(403) 125-2552",
"City": "Coevorden",
"Name": "Grady"
},
{"ID": 2,
"Phone": "(403) 125-2552",
"City": "Coevorden",
"Name": "Grady"
},
{
"ID": 3,
"parentID": [1,2],
"Phone": "(979) 486-1932",
"City": "Chełm",
"Name": "Scarlet"
}];
const expectedDataTree = [
{
"ID":1,
"Phone":"(403) 125-2552",
"City":"Coevorden",
"Name":"Grady",
"childNodes":[{
"ID":2,
"parentID":[1,3],
"Phone":"(979) 486-1932",
"City":"Chełm",
"Name":"Scarlet",
"childNodes":[]
}]
},
{
"ID":3,
"parentID":[],
"Phone":"(403) 125-2552",
"City":"Coevorden",
"Name":"Grady",
"childNodes":[
{
"ID":2,
"parentID":[1,3],
"Phone":"(979) 486-1932",
"City":"Chełm",
"Name":"Scarlet",
"childNodes":[]
}
]
}
];
const createDataTree = dataset => {
const hashTable = Object.create(null);
dataset.forEach(aData => hashTable[aData.ID] = {...aData, childNodes: []});
const dataTree = [];
dataset.forEach(Datae => {
if (Datae.parentID && Datae.parentID.length > 0) {
Datae.parentID.forEach( aData => {
hashTable[aData].childNodes.push(hashTable[Datae.ID])
});
}
else{
dataTree.push(hashTable[Datae.ID])
}
});
return dataTree;
};
window.alert(JSON.stringify(createDataTree(dataSet)));
答案 23 :(得分:0)
我编写了一个测试脚本,以评估两种最通用的解决方案的性能(这意味着输入不必事先排序,并且代码不依赖于第三方库),由用户shekhardtu提出( see answer和FurkanO(see answer)。
http://playcode.io/316025?tabs=console&script.js&output
FurkanO的解决方案似乎是最快的。
答案 24 :(得分:0)
这是我在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"
}
]
}
]```
答案 25 :(得分:0)
这是一个旧线程,但是我认为更新永远都不会造成伤害,使用ES6可以做到:
(parent => categories.filter(cat => cat. parentId === parent).map(cat=>({...cat, children: recursive1(cat.id)})))(0)
希望它对某人有帮助
答案 26 :(得分:0)
您可以使用npm软件包“数组到树https://github.com/alferov/array-to-tree”。 它将普通的节点数组(带有指向父节点的指针)转换为嵌套的数据结构。
解决了从数据库数据集到嵌套数据结构(即导航树)的转换问题。
用法:
var arrayToTree = require('array-to-tree');
var dataOne = [
{
id: 1,
name: 'Portfolio',
parent_id: undefined
},
{
id: 2,
name: 'Web Development',
parent_id: 1
},
{
id: 3,
name: 'Recent Works',
parent_id: 2
},
{
id: 4,
name: 'About Me',
parent_id: undefined
}
];
arrayToTree(dataOne);
/*
* Output:
*
* Portfolio
* Web Development
* Recent Works
* About Me
*/
答案 27 :(得分:0)
从Internet复制 http://jsfiddle.net/stywell/k9x2a3g6/
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
parameters:
locale: en
uploads_directory: '%kernel.root_dir%/../uploads'
framework:
#esi: ~
#translator: { fallbacks: ["%locale%"] }
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
#serializer: { enable_annotations: true }
templating:
engines: ['twig']
default_locale: "%locale%"
trusted_hosts: ~
trusted_proxies: ~
session:
# http://symfony.com/doc/current/reference/configuration/framework.html#handler-id
handler_id: session.handler.native_file
save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
fragments: ~
http_method_override: true
assets:
version: 15
# Twig Configuration
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
form_themes: ["form/fields.html.twig"]
globals:
user: "@app.user"
badges: '@app.sidebar_badges'
paths:
'%kernel.root_dir%/../src/AppBundle/Resources/views': ~
'%kernel.root_dir%/../src/AppBundle/Resources/TwigBundle/views': 'Twig'
答案 28 :(得分:0)
这是上面的修改版本,适用于多个根项目,我使用GUID作为我的id和parentIds所以在创建它们的UI中我将根项目硬编码为0000000-00000-00000-TREE-ROOT -Item
var tree = unflatten(记录,“TREE-ROOT-ITEM”);
function unflatten(records, rootCategoryId, parent, tree){
if(!_.isArray(tree)){
tree = [];
_.each(records, function(rec){
if(rec.parentId.indexOf(rootCategoryId)>=0){ // change this line to compare a root id
//if(rec.parentId == 0 || rec.parentId == null){ // example for 0 or null
var tmp = angular.copy(rec);
tmp.children = _.filter(records, function(r){
return r.parentId == tmp.id;
});
tree.push(tmp);
//console.log(tree);
_.each(tmp.children, function(child){
return unflatten(records, rootCategoryId, child, tree);
});
}
});
}
else{
if(parent){
parent.children = _.filter(records, function(r){
return r.parentId == parent.id;
});
_.each(parent.children, function(child){
return unflatten(records, rootCategoryId, child, tree);
});
}
}
return tree;
}
答案 29 :(得分:0)
以下是Steven Harris的修改版本&#39;这是普通的ES5并返回一个键入id的对象,而不是返回顶层和子节点的节点数组。
unflattenToObject = function(array, parent) {
var tree = {};
parent = typeof parent !== 'undefined' ? parent : {id: 0};
var childrenArray = array.filter(function(child) {
return child.parentid == parent.id;
});
if (childrenArray.length > 0) {
var childrenObject = {};
// Transform children into a hash/object keyed on token
childrenArray.forEach(function(child) {
childrenObject[child.id] = child;
});
if (parent.id == 0) {
tree = childrenObject;
} else {
parent['children'] = childrenObject;
}
childrenArray.forEach(function(child) {
unflattenToObject(array, child);
})
}
return tree;
};
var arr = [
{'id':1 ,'parentid': 0},
{'id':2 ,'parentid': 1},
{'id':3 ,'parentid': 1},
{'id':4 ,'parentid': 2},
{'id':5 ,'parentid': 0},
{'id':6 ,'parentid': 0},
{'id':7 ,'parentid': 4}
];
tree = unflattenToObject(arr);
答案 30 :(得分:-1)
答案 31 :(得分:-1)
尝试一下
function getUnflatten(arr,parentid){
let output = []
for(const obj of arr){
if(obj.parentid == parentid)
let children = getUnflatten(arr,obj.id)
if(children.length){
obj.children = children
}
output.push(obj)
}
}
return output
}