How to convert an array to a hierarchical array

I have some data that

var currentData = [ {'ticket':'CAP', 'child':'CT-1'}, {'ticket':'CAP', 'child':'CT-2'}, {'ticket':'CT-1', 'child':'CT-1-A'}, {'ticket':'CT-1', 'child':'CT-1-B'} ]; 

The data is flat and I need to convert it to something like:

 { 'ticket': 'CAP', children : [{ 'ticket' : 'CT-1', 'children' : [{ 'ticket' : 'CT-1-A', 'children' : [] }, { 'ticket' : 'CT-1-B', 'children' : [] }], [{ 'ticket' : 'CT-2', 'children' : [] }] }] } 

(I think this is true)?

I am very lost, like. I'm going to show my efforts, but I'm not sure if my approach is right or not.

 var currentData = [{'ticket':'cap', 'child':'CT-1'},{'ticket':'cap', 'child':'CT-2'}, {'ticket':'CT-1', 'child':'CT-1-A'},{'ticket':'CT-1', 'child':'CT-1-B'}]; var newList = []; function convert(list){ if (newList.length <= 0){ var child = []; var emptyChild = []; child.push({'ticket': list[0].child, 'child': emptyChild }); newList.push({'ticket': list[0].ticket, 'children' : child}); list.splice(0,1); } // the if statement above works fine for(var i = 0; i < list.length; i++) { var ticket = list[i].ticket; for(var j = 0; j < newList.length; j++) { if (newList[j].ticket == ticket){ var child; var emptyChild = []; child = {'ticket': list[i].child, 'child': emptyChild }; newList[j].children.push(child); list.splice(i,1); break; } // the if above works else{ var child2 = getFromChildren(ticket, newList, list[i]); // child2 is Always null, even if getFromChildren returns an object newList[j].children.push(child2); list.splice(i,1); break; } } } if (list.length > 0){ convert(list); } } function getFromChildren(ticket, list, itemToAdd){ if (list == null || list[0].children == null) return; for(var i = 0; i < list.length; i++) { if (list[i] == null) return; if (list[i].ticket == ticket){ list[i].child.push(itemToAdd.child); // ** can't do this, javascript passes by value, not by reference :( } else{ getFromChildren(ticket, list[i].children, itemToAdd); } } } convert(currentData); 

I think I ruined everything. In the comments, I put ** an explanation that it does not work due to the fact that JavaScript is not passed by reference, however on further reading I do not think that it is correct when I pass an object which is by reference?

Edit

Data shown with currentData will not always start at the root, or

+5
source share
5 answers

 function convert(arr) { var children = {}; // this object will hold a reference to all children arrays var res = arr.reduce(function(res, o) { // for each object o in the array arr if(!res[o.ticket]) { // if there is no object for the element o.ticket res[o.ticket] = {ticket: o.ticket, children: []}; // then creates an object for it children[o.ticket] = res[o.ticket].children; // and store a reference to its children array } if(!res[o.child]) { // if there is no object for the element o.child res[o.child] = {ticket: o.child, children: []}; // then creates an object for it children[o.child] = res[o.child].children; // and store a reference to its children array } return res; }, {}); arr.forEach(function(o) { // now for each object o in the array arr children[o.ticket].push(res[o.child]); // add the object of o.child (from res) to its children array delete res[o.child]; // and remove the child object from the object res }); return res; } var currentData = [ {'ticket':'CAP', 'child':'CT-1'}, {'ticket':'CAP', 'child':'CT-2'}, {'ticket':'CT-1', 'child':'CT-1-A'}, {'ticket':'CT-1', 'child':'CT-1-B'} ]; console.log(convert(currentData)); 

Explanation:

The reduce part creates a form object: { ticket: "...", children: [] } for each element (child or not). So, immediately after reduce the res object will be:

 res = { 'CAP': { ticket: 'CAP', children: [] }, 'CT-1': { ticket: 'CT-1', children: [] }, 'CT-2': { ticket: 'CT-2', children: [] }, 'CT-1-A': { ticket: 'CT-1-A', children: [] }, 'CT-1-B': { ticket: 'CT-1-B', children: [] }, } 

Now comes the forEach bit, which .child over the array again, and now for each object it extracts the .child object from res above, pushes it into the .ticket object children (which the reference to it is stored in the children object), then delete the .child object from the res object .

+2
source

The abbreviation below is used to get the data grouped by map, and then I will convert the data to an object, as shown above. You will need a modern browser to work under fragments or use a transpiler, for example, babeljs, to convert it to es5 syntax.

 let currentData = [ {'ticket':'CAP', 'child':'CT-1'}, {'ticket':'CAP', 'child':'CT-2'}, {'ticket':'CT-1', 'child':'CT-1-A'}, {'ticket':'CT-1', 'child':'CT-1-B'} ]; let children = currentData.map(e => e.child); currentData.sort((a,b) => children.indexOf(a.ticket)); let res = currentData.reduce((a,b) => { if (! children.includes(b.ticket)) { return a.set(b.ticket, (a.get(b.ticket) || []) .concat({ticket: b.child, children: currentData .filter(el => el.ticket === b.child) .map(el => ({ticket: el.child, children: []}))})) } return a; }, new Map); let r = {}; for (let [key,value] of res.entries()) { r.ticket = key; r.children = value; } console.log(r); 
+1
source

A solution using recursion starting with node can be changed.

 var currentData = [{'ticket': 'cap','child': 'CT-1'}, {'ticket': 'cap','child': 'CT-2'}, {'ticket': 'CT-1','child': 'CT-1-A'}, {'ticket': 'CT-1','child': 'CT-1-B'}]; function convert(data, start){ return { ticket: start, childs: data.filter(d => d.ticket == start) .reduce((curr, next) => curr.concat([next.child]), []) .map(c => convert(data, c)) } } let result = convert(currentData, 'cap'); console.log(result); 
 .as-console-wrapper{top: 0; max-height: none!important;} 
+1
source

I would go with a simple for approach, for example:

 var currentData = [ {'ticket':'CAP', 'child':'CT-1'}, {'ticket':'CAP', 'child':'CT-2'}, {'ticket':'CT-1', 'child':'CT-1-A'}, {'ticket':'CT-1', 'child':'CT-1-B'} ]; var leafs = {}; var roots = {}; var tickets = {}; for(var i=0; i<currentData.length; i++){ var ticket = currentData[i].ticket; var child = currentData[i].child; if(!tickets[ticket]){ tickets[ticket] = {ticket:ticket,children:[]}; if(!leafs[ticket]){ roots[ticket] = true; } } if(!tickets[child]){ tickets[child] = {ticket:child,children:[]}; } delete roots[child]; leafs[child] = true; tickets[ticket].children.push(tickets[child]); } for(var ticket in roots){ console.log(tickets[ticket]); } 
+1
source

Well, if you are not familiar with reduce , map , forEach with callbacks for iteration, then here is the approach I came up with, where the code is flat, storing links to objects in another map object and iterating exactly once the original array.

The code is much cleaner, if something is clear, add comments, I will explain;

 var currentData = [ {'ticket':'CT-1', 'child':'CT-1-A'}, {'ticket':'CT-1', 'child':'CT-1-B'}, {'ticket':'CAP', 'child':'CT-1'}, {'ticket':'CAP', 'child':'CT-2'} ]; function buildHierarchy(flatArr) { let root = {}, nonRoot = {}, tempMap = {}; Object.setPrototypeOf(root, nonRoot); for (let idx = 0; idx < flatArr.length; idx++) { let currTicket = flatArr[idx]; let tempTicket = tempMap[currTicket.ticket] || {ticket: currTicket.ticket, children: []}; tempMap[currTicket.ticket] = tempTicket; if (currTicket.child) { let tempChild = tempMap[currTicket.child] || {ticket: currTicket.child, children: []}; tempTicket.children.push(tempChild); tempMap[currTicket.child] = tempChild; delete root[tempChild.ticket]; nonRoot[tempChild.ticket] = true; } root[tempTicket.ticket] = true; } return tempMap[Object.keys(root)[0]]; } console.log(buildHierarchy(currentData)); 

I changed the sequence of your source array to place the root object anywhere, and the code should work on that.

0
source

Source: https://habr.com/ru/post/1271845/


All Articles