我有一个复杂的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
            }
        }

    ]
}


评论

有几种方法可以做到,您还尝试了吗?

我假设parentId为0意味着没有父级id,应该位于顶层。

通常,这类任务需要广泛的工作知识对象。好问题

#1 楼

如果使用地图查找,则有一个有效的解决方案。如果父母总是先于孩子,则可以合并两个for循环。它支持多个根。它在悬垂的分支上给出错误,但可以修改以忽略它们。它不需要第三方库。据我所知,这是最快的解决方案。



 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",
    "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
  }
];

console.log(list_to_tree(entries)); 




如果您对复杂性理论感兴趣,则此解决方案为Θ(n log(n))。递归滤波器的解决方案是Θ(n ^ 2),这对于大数据集可能是个问题。

评论


请记住,使用此解决方案时,必须对节点进行特定排序,以确保先将父节点推入地图,否则查找过程将出错...因此,您需要在level属性上对em进行排序,或者您需要首先将它们推入地图。并使用单独的for循环进行查找。 (我更喜欢排序,但是当您没有级别属性时,可以选择单独的循环)

–桑德
13年15月15日在18:55

起初,我发现令人惊讶的是,没有其他信息,例如:[1,5,6]这样的路径,其中数组是后续祖先,无法在其中有效使用。但是看代码有点感觉,因为我相信它是O(n)

– Ced
17年4月3日在10:44

尽管答案很好,但它很复杂。仅针对两个行代码应用我的答案:链接

–伊曼(Iman Bahrampour)
17年8月26日在10:25



请您能解释一下为什么这个解是Θ(n log(n)),这似乎要花O(n)的时间。

– Amrender Singh
18 Mar 7 '18 at 7:34

在地图中@Halcyon查找需要恒定的时间,即O(1)。

– Amrender Singh
18年8月8日在18:17

#2 楼

正如@Sander所提到的那样,@ Halcyon的答案假设一个预先排序的数组,以下内容则不是。 (尽管它确实假定您已加载underscore.js-尽管它可以用普通的javascript编写):

代码




 // Example usage
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}
];

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;
}

tree = unflatten( arr );
document.body.innerHTML = "<pre>" + (JSON.stringify(tree, null, " ")) 

 <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script> 





需求

它假定属性“ id”和“ parentid”分别表示ID和父ID。必须有父ID为0的元素,否则您将获得一个空数组。孤立的元素及其后代是“丢失的”

http://jsfiddle.net/LkkwH/1/

评论


您可以添加else {parent ['children'] = []; }在确保每个节点都有子属性的第一个if子句之后(如果该节点是叶节点,则为空)

–克里斯托弗(Christopher)
16年4月12日在10:20

您的代码段运行良好,谢谢!!唯一的事情是:在递归调用函数时,tree从不作为参数传递,所以我认为line tree = typeof tree!=='undefined'吗?树:[];可以用let tree = []代替;

–奥斯卡·卡尔德隆(Oscar Calderon)
17年1月12日在13:19

可以将其修改为允许null的parent_id而不是0吗?编辑:没关系,我通过将id:0更改为id:null来工作。

– dlinx90
17年1月23日在8:34



请记住,以上答案使用两个循环,因此可以加以改进。由于我找不到实现O(n)解决方案的npm模块,因此我创建了以下模块(经过测试的单元,100%的代码覆盖率,大小仅为0.5 kb,包括键入内容)。也许对某人有帮助:npmjs.com/package/performant-array-to-tree

–菲利普·斯坦尼斯劳斯(Philip Stanislaus)
17年5月7日在17:42

对于任何感兴趣的人,该代码都可以轻松转换为原始js:jsfiddle.net/LkkwH/853

–xec
18年4月12日在9:58

#3 楼

(BONUS1:可以点或不可以点)(BONUS2:不需要3RD党库,普通JS)
(BONUS3:用户“ Elias Rabl”说这是最快的解决方案,请参见下面的答案)
这里是:
const createDataTree = dataset => {
  const hashTable = Object.create(null);
  dataset.forEach(aData => hashTable[aData.ID] = {...aData, childNodes: []});
  const 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', () => {
  const dataSet = [{
    "ID": 1,
    "Phone": "(403) 125-2552",
    "City": "Coevorden",
    "Name": "Grady"
  }, {
    "ID": 2,
    "parentID": 1,
    "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,
      "Phone": "(979) 486-1932",
      "City": "Chełm",
      "Name": "Scarlet",
      childNodes : []
    }]
  }];

  expect(createDataTree(dataSet)).toEqual(expectedDataTree);
});


评论


如果仅在需要时添加childNodes会更准确吗?通过将它们从第一个forEach中移出并在第二个中移动它们?

– arpl
19-10-15在15:15



@FurkanO确实是一个不错的解决方案,但是通过函数式编程(不进行任何突变),有可能获得接近该性能的任何结果

– Dac0d3r
4月29日20:54

#4 楼

使用此ES6方法。像魅力一样工作




 // Data Set
// One top level comment 
const 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) }));

console.log(
  nest(comments)
) 




评论


我认为最短和最好的答案

–java-man-script
19年11月29日在7:38

sloooow与FurkanO的答案相比

– Geza Turi
19/12/17在17:44

#5 楼

遇到相同的问题,但是我不确定数据是否已排序。我无法使用3rd party库,所以这只是香草Js。输入数据可以从@Stephen的示例获取;




  var arr = [
        {'id':1 ,'parentid' : 0},
        {'id':4 ,'parentid' : 2},
        {'id':3 ,'parentid' : 1},
        {'id':5 ,'parentid' : 0},
        {'id':6 ,'parentid' : 0},
        {'id':2 ,'parentid' : 1},
        {'id':7 ,'parentid' : 4},
        {'id':8 ,'parentid' : 1}
      ];
    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;
    }

var tree = unflatten(arr);
document.body.innerHTML = "<pre>" + (JSON.stringify(tree, null, " ")) 





JS小提琴

平面数组到树

评论


在某些情况下,mappedArr [mappedElem ['parentid']] ['children']失败,因为无法访问未定义的孩子。

– Al-Mothafar
18年9月6日在13:36

我将如何从父ID:1开始?

– Vinni
18-10-18在12:54

#6 楼

一个更简单的功能列表到树的精简版

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;
}


jsfiddle

#7 楼

您只需执行以下两行编码即可解决此问题:

_(flatArray).forEach(f=>
           {f.nodes=_(flatArray).filter(g=>g.parentId==f.id).value();});

var resultArray=_(flatArray).filter(f=>f.parentId==null).value();


在线测试(请参阅浏览器控制台以获取所创建的树)

要求:

1-安装lodash 4(用于使用高性能方法操作对象和集合的Javascript库=>像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:[]
    }];


感谢巴赫沙巴迪先生

祝你好运

#8 楼

可能有用的从列表到树的软件包
安装:

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
}];


评论


请注意,不鼓励仅链接的答案,因此,SO答案应该是搜索解决方案的终点(与引用的另一种中途停留相比,随着时间的流逝,它们往往会过时)。请考虑在此处添加独立的简介,并保留链接作为参考

– kleopatra
2015年9月1日在8:06

我不明白为什么-1,我认为这是一个很好的解决方案,但不幸的是我没有在gitHub或其他公共存储库中找到该软件包

–oriaj
15年10月16日在21:01

感谢您对包裹的关注。我计划以后再扩展它。这是到仓库github.com/DenQ/list-to-tree的链接

– DenQ
15-10-17在8:13



@oriaj我很高兴该项目受益。一些想法的计划

– DenQ
15-10-19在17:25



很好,谢谢@DenQ。希望它有更多的测试范围!

– IliasT
2015年10月23日下午5:13

#9 楼

我编写了一个测试脚本,以评估用户shekhardtu提出的两个最通用的解决方案的性能(这意味着输入不必事先排序,并且代码不依赖于第三方库)(请参见答案)和FurkanO(请参见答案)。

http://playcode.io/316025?tabs=console&script.js&output

FurkanO的解决方案似乎是最快的。




 /*
** performance test for https://stackoverflow.com/questions/18017869/build-tree-array-from-flat-array-in-javascript
*/

// Data Set (e.g. nested comments)
var comments = [{
    id: 1,
    parent_id: null
}, {
    id: 2,
    parent_id: 1
}, {
    id: 3,
    parent_id: 4
}, {
    id: 4,
    parent_id: null
}, {
    id: 5,
    parent_id: 4
}];

// add some random entries
let maxParentId = 10000;
for (let i=6; i<=maxParentId; i++)
{
  let randVal = Math.floor((Math.random() * maxParentId) + 1);
  comments.push({
    id: i,
    parent_id: (randVal % 200 === 0 ? null : randVal)
  });
}

// solution from user "shekhardtu" (https://stackoverflow.com/a/55241491/5135171)
const nest = (items, id = null, link = 'parent_id') =>
  items
    .filter(item => item[link] === id)
    .map(item => ({ ...item, children: nest(items, item.id) }));
;

// solution from user "FurkanO" (https://stackoverflow.com/a/40732240/5135171)
const createDataTree = dataset => {
    let hashTable = Object.create(null)
    dataset.forEach( aData => hashTable[aData.id] = { ...aData, children : [] } )
    let dataTree = []
    dataset.forEach( aData => {
      if( aData.parent_id ) hashTable[aData.parent_id].children.push(hashTable[aData.id])
      else dataTree.push(hashTable[aData.id])
    } )
    return dataTree
};


/*
** lets evaluate the timing for both methods
*/
let t0 = performance.now();
let createDataTreeResult = createDataTree(comments);
let t1 = performance.now();
console.log("Call to createDataTree took " + Math.floor(t1 - t0) + " milliseconds.");

t0 = performance.now();
let nestResult = nest(comments);
t1 = performance.now();
console.log("Call to nest took " + Math.floor(t1 - t0) + " milliseconds.");




//console.log(nestResult);
//console.log(createDataTreeResult);

// bad, but simple way of comparing object equality
console.log(JSON.stringify(nestResult)===JSON.stringify(createDataTreeResult)); 




#10 楼

这是对无序项目的建议。此函数可用于单个循环和哈希表,并使用其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; } 




#11 楼

也可以使用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();
}
 


#12 楼

我喜欢@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;
}


示例:
https: //jsfiddle.net/kqw1qsf0/17/

#13 楼




 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;
	}
} 




评论


我创建了此函数,以将数据从对象数组转换为树结构,这是d3树交互式图表所必需的。仅用40行代码,我就能获得输出。我在js中以高效的递归函数编写了此函数。尝试让我知道您的反馈。谢谢!!!!

–karthik reddy
17年10月31日在19:18

感谢anwser。.它非常适合我的d3树拓扑。.现在,我需要根据节点的值更改节点颜色。.为此,我需要在JSON中传递标志值。我该怎么做。.{“名称”:“顶级”,“标志”:1,“父母”:“空”,“孩子”:[{“名称”:“印度”,“标志”:0 ,“父母”:“顶级”,“孩子”:[

–Puneeth Kumar
18年7月30日在7:04



#14 楼

几天前,当我不得不从平面阵列中显示文件夹树时,我遇到了类似的问题。我在TypeScript中没有看到任何解决方案,因此希望对您有所帮助。

在我的情况下,主要父对象只有一个,而且rawData数组也不必排序。基于准备临时对象的解决方案,例如
{parentId: [child1, child2, ...] }

示例原始数据

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;
    }


#15 楼

我基于@Halcyon回答编写了ES6版本。

 const array = [
  {
    id: '12',
    parentId: '0',
    text: 'one-1'
  },
  {
    id: '6',
    parentId: '12',
    text: 'one-1-6'
  },
  {
    id: '7',
    parentId: '12',
    text: 'one-1-7'
  },

  {
    id: '9',
    parentId: '0',
    text: 'one-2'
  },
  {
    id: '11',
    parentId: '9',
    text: 'one-2-11'
  }
];

// Prevent changes to the original data
const arrayCopy = array.map(item => ({ ...item }));

const listToTree = list => {
  const map = {};
  const roots = [];

  list.forEach((v, i) => {
    map[v.id] = i;
    list[i].children = [];
  });

  list.forEach(v => (v.parentId !== '0' ? list[map[v.parentId]].children.push(v) : roots.push(v)));

  return roots;
};

console.log(listToTree(arrayCopy));
 


该算法的原理是使用“地图”建立索引关系。很容易通过“ parentId”在列表中找到“ item”,并将“ children”添加到每个“ item”,因为“ list”是引用关系,因此“ roots”将与整个树建立关系。 />

#16 楼

基于@FurkanO的答案,我创建了另一个不会改变原始数据的版本(例如,要求的@ Dac0d3r)。我真的很喜欢@shekhardtu的答案,但意识到它必须多次过滤数据。我认为解决方案可能是先复制数据,然后使用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为falsy(nullundefined或其他falsy值)或父对象不存在,则我们将该对象视为根节点。

#17 楼

这是我根据上述答案创建的一个简单的辅助函数,是针对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

}


#18 楼

这是史蒂文·哈里斯(Steven Harris)的修改版本,它是普通的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);


#19 楼

这是上面的修改版本,可与多个根项一起使用,我将GUID用于ID和parentId,因此在创建它们的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;
}


#20 楼

从Internet复制
http://jsfiddle.net/stywell/k9x2a3g6/

    function list2tree(data, opt) {
        opt = opt || {};
        var KEY_ID = opt.key_id || 'ID';
        var KEY_PARENT = opt.key_parent || 'FatherID';
        var KEY_CHILD = opt.key_child || 'children';
        var EMPTY_CHILDREN = opt.empty_children;
        var ROOT_ID = opt.root_id || 0;
        var MAP = opt.map || {};
        function getNode(id) {
            var node = []
            for (var i = 0; i < data.length; i++) {
                if (data[i][KEY_PARENT] == id) {
                    for (var k in MAP) {
                        data[i][k] = data[i][MAP[k]];
                    }
                    if (getNode(data[i][KEY_ID]) !== undefined) {
                        data[i][KEY_CHILD] = getNode(data[i][KEY_ID]);
                    } else {
                        if (EMPTY_CHILDREN === null) {
                            data[i][KEY_CHILD] = null;
                        } else if (JSON.stringify(EMPTY_CHILDREN) === '[]') {
                            data[i][KEY_CHILD] = [];
                        }
                    }
                    node.push(data[i]);
                }
            }
            if (node.length == 0) {
                return;
            } else {
                return node;
            }
        }
        return getNode(ROOT_ID)
    }

    var opt = {
        "key_id": "ID",              //节点的ID
        "key_parent": "FatherID",    //节点的父级ID
        "key_child": "children",     //子节点的名称
        "empty_children": [],        //子节点为空时,填充的值  //这个参数为空时,没有子元素的元素不带key_child属性;还可以为null或者[],同理
        "root_id": 0,                //根节点的父级ID
        "map": {                     //在节点内映射一些值  //对象的键是节点的新属性; 对象的值是节点的老属性,会赋值给新属性
            "value": "ID",
            "label": "TypeName",
        }
    };


#21 楼

您可以使用npm软件包array-to-tree 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
 */


#22 楼

这就是我在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"
         }
      ]
   }
]```


#23 楼

您可以从Github或NPM使用此“ treeify”程序包。

安装:

$ npm install --save-dev treeify-js

#24 楼

我的打字稿解决方案可能对您有帮助:

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');


#25 楼

将节点数组转换为树

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 Elements:

 /**
 * 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> 




#26 楼

回答类似的问题:
https://stackoverflow.com/a/61575152/7388356
UPDATE
您可以使用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);



评论


尽管此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效。 -来自评论

– JeffRSon
5月3日15:17

好的,添加了主要思想并给出了示例示例,

–Yusufbek
5月3日下午16:26

#27 楼

我的解决方案:

允许双向映射(从根到叶,从叶到根)
返回所有节点,根和叶
一次数据传递和非常快的性能
/>香草Javascript

 /**
 * 
 * @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);
 


#28 楼

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);


#29 楼


无需第三方库
无需预订数组
就可以得到想要的树的任何部分

尝试一下

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
 }


在Jsfiddle上进行测试

#30 楼

这是一个旧线程,但是我认为进行更新绝对不会造成任何伤害,使用ES6可以做到:



 const data = [{
    id: 1,
    parent_id: 0
}, {
    id: 2,
    parent_id: 1
}, {
    id: 3,
    parent_id: 1
}, {
    id: 4,
    parent_id: 2
}, {
    id: 5,
    parent_id: 4
}, {
    id: 8,
    parent_id: 7
}, {
    id: 9,
    parent_id: 8
}, {
    id: 10,
    parent_id: 9
}];

const arrayToTree = (items=[], id = null, link = 'parent_id') => items.filter(item => id==null ? !items.some(ele=>ele.id===item[link]) : item[link] === id ).map(item => ({ ...item, children: arrayToTree(items, item.id) }))
const temp1=arrayToTree(data)
console.log(temp1)

const treeToArray = (items=[], key = 'children') => items.reduce((acc, curr) => [...acc, ...treeToArray(curr[key])].map(({ [`${key}`]: child, ...ele }) => ele), items);
const temp2=treeToArray(temp1)

console.log(temp2) 




希望对别人有帮助