sort json objects into nested tree - javascript

I'm pulling two related objects from a web service - folders and emails. Folders have an ID and a parentfolder.ID property which indicates which parent folder a folder is nested beneath. Emails have a CategoryID which indicates which folder it is a child of.
I've successfully created a function to nest the emails within a flat folder structure:
{
"folders": [
{
"name": "my emails",
"type": "folder",
"additionalParameters": {
"id": "174661",
"type": "email",
"parentID": "0"
},
"children": [
{
"name": "Test1",
"type": "item",
"additionalParameters": {
"id": "27502",
"subject": "Test"
}
},
{
"name": "Hello",
"type": "item",
"additionalParameters": {
"id": "27917",
"subject": "Hi!"
}
}
]
},
{
"name": "Test",
"type": "folder",
"additionalParameters": {
"id": "175620",
"type": "email",
"parentID": "174661"
},
"children": [
{
"name": "Test2",
"type": "item",
"additionalParameters": {
"id": "27891",
"subject": "Test"
}
}
]
},
{
"name": "SubFolder1",
"type": "folder",
"additionalParameters": {
"id": "175621",
"type": "email",
"parentID": "175620"
},
"children": [
{
"name": "Test2",
"type": "item",
"additionalParameters": {
"id": "27892",
"subject": "Test"
}
},
{
"name": "Test3",
"type": "item",
"additionalParameters": {
"id": "27893",
"subject": "Test"
}
}
]
},
{
"name": "SubFolder2",
"type": "folder",
"additionalParameters": {
"id": "175622",
"type": "email",
"parentID": "175620"
},
"children": [
{
"name": "Test4",
"type": "item",
"additionalParameters": {
"id": "27894",
"subject": "Test"
}
}
]
}
]
}
Now I need to use recursion to loop through all of the folders and push them into the children array of their parent. Essentially resorting the tree to n levels. I can disregard any type=items because they are already nested appropriately. Just need to sort those whose types are folder.
Has anyone implemented a JSON recursion function to rebuild a JSON object with nesting?
Thanks for the help.

You can do this without recursion. I answered a similar question sometime back. I believe you could use the same approach (assuming you have no forward references):
var idToNodeMap = {}; //Keeps track of nodes using id as key, for fast lookup
var root = null; //Initially set our root to null
//loop over data
for(var i = 0; i < data.folders.length; i++) {
var folder = data.folders[i];
//each node will have children, so let's give it a "children" poperty
folder.children = [];
//add an entry for this node to the map so that any future children can
//lookup the parent
idToNodeMap[folder.additionalParameters.id] = folder;
//Does this node have a parent?
if(folder.additionalParamters.parentID === "0") {
//Doesn't look like it, so this node is the root of the tree
root = folder;
} else {
//This node has a parent, so let's look it up using the id
parentNode = idToNodeMap[folder.additionalParamters.parentID];
//Let's add the current node as a child of the parent node.
parentNode.children.push(folder);
}
}

Thanks to Vivin. Via his answer I found a link to an approach that ended up working. Here's the final code:
var arr = $this.folderArray;
// Define root
console.log("arr");
console.log(arr);
// Define tree
var tree = {
root: root
};
console.log('tree');
console.log(tree);
// Get parent of node (recursive)
var getParent = function (rootNode, rootId) {
console.log('rootnode');
console.log(rootNode);
console.log('rootId');
console.log(rootId);
if (rootNode.additionalParameters.id === rootId)
return rootNode;
for (var i = 0; i < rootNode.children.length; i++) {
var child = rootNode.children[i];
if (child.additionalParameters.id === rootId) return child;
if(child.children){
if (child.children.length > 0){
var childResult = getParent(child, rootId);
if (childResult != null) return childResult;
}
}
}
return null;
};
// Traverse data and build the tree
var buildTree = function(tree) {
for (var i = 0; i < arr.length; i++) {
var elem = arr[i];
if (elem.additionalParameters.parentID === "0")
continue;
//elem["children"] = [];
var rootId = elem.additionalParameters.parentID;
var parent = getParent(tree.root, rootId);
console.log("parent");
console.log(parent);
parent.children.push(elem);
// Debug info
// console.log("Elem: " + elem.name + " with parent_id: " + elem.parentAreaRef.id);
//console.log("Got parent with name: " + parent._id);
}
};
buildTree(tree);

Related

Push object into an array of objects nested within an array of objects

I am trying to push the values "label" and "link" into an object within "data" where the target is the object with an id that is equal to the "parent" value of another object. These values should be pushed into the "children" property of the matching target object. This does not appear to be working. Any pointers?
var data = [
{
"id": 0,
"label": "example page0",
"link": "/apx/...",
"icon": "..",
"parent": null
"children": null
},
{
"id": 1,
"label": "example page1",
"link": "/apx/...",
"icon": "notes",
"parent": null
"children": null
},
{
"id": 2,
"label": "example page2",
"link": "/apx/....",
"icon": "...",
"parent": null
"children": null
},
{
"id": 3,
"label": "example subpage3",
"link": "/apx/....",
"icon": "...",
"parent": 2
"children": null
},
{
"id": 4,
"label": "example subpage4",
"link": "/apx/....",
"icon": "...",
"parent": 2
"children": null
}]
for (let entry of data) {
if (entry.parent > 0) {
var index = data.findIndex(x => x.id == entry.parent);
data[index].children.push({ label: entry.label, link: entry.link })
}
}
Expected output:
[
{
"id": 0,
"label": "example page0",
"link": "/apx/...",
"icon": "..",
"parent": null
"children": null
},
{
"id": 1,
"label": "example page1",
"link": "/apx/...",
"icon": "notes",
"parent": null
"children": null
},
{
"id": 2,
"label": "example page2",
"link": "/apx/....",
"icon": "...",
"parent": null
"children": [
{ "label": "example subpage3", "link": "/apx/...." },
{ "label": "example subpage4", "link": "/apx/...." }
]
}
]
You can implement it by using Array.prototype.reduce. The reduce will iterate over the data array and find elements having the parent property which are not null and find its parent from the data array by searching with the id property.
Now you need to check whether the children property is existing or not, if not you need to create a new array object and assign to the children property, else just append to existing children array:
const data = [{"id":0,"label":"example page0","link":"/apx/...","icon":"..","parent":null,"children":null},{"id":1,"label":"example page1","link":"/apx/...","icon":"notes","parent":null,"children":null},{"id":2,"label":"example page2","link":"/apx/....","icon":"...","parent":null,"children":null},{"id":3,"label":"example subpage3","link":"/apx/....","icon":"...","parent":2,"children":null},{"id":4,"label":"example subpage4","link":"/apx/....","icon":"...","parent":2,"children":null}]
const res = data.reduce((acc, entry, idx, data) => {
if (entry.parent > 0) {
const matchingParent = data.find(e => e.id === entry.parent);
if (matchingParent) {
const child = {
label: entry.label,
link: entry.link
};
if (matchingParent.children) {
matchingParent.children.push(child)
} else {
matchingParent.children = [child];
}
}
} else {
acc.push(entry);
}
return acc;
}, []);
console.log(res);
You can also do it using a for..of loop also:
const data = [{"id":0,"label":"example page0","link":"/apx/...","icon":"..","parent":null,"children":null},{"id":1,"label":"example page1","link":"/apx/...","icon":"notes","parent":null,"children":null},{"id":2,"label":"example page2","link":"/apx/....","icon":"...","parent":null,"children":null},{"id":3,"label":"example subpage3","link":"/apx/....","icon":"...","parent":2,"children":null},{"id":4,"label":"example subpage4","link":"/apx/....","icon":"...","parent":2,"children":null}];
const acc = [];
for (let entry of data) {
if (entry.parent > 0) {
const matchingParent = data.find(e => e.id === entry.parent);
if (matchingParent) {
const child = {
label: entry.label,
link: entry.link
};
if (matchingParent.children) {
matchingParent.children.push(child)
} else {
matchingParent.children = [child];
}
}
} else {
acc.push(entry);
}
}
console.log(acc);
This is when processing needs to happen in-place. In that case we find elements with non-null parents we can add those as children to the parent element and remove those from the data array using splice.
Iterating backwards as the splice will change the length property of the data array:
const data = [{"id":0,"label":"example page0","link":"/apx/...","icon":"..","parent":null,"children":null},{"id":1,"label":"example page1","link":"/apx/...","icon":"notes","parent":null,"children":null},{"id":2,"label":"example page2","link":"/apx/....","icon":"...","parent":null,"children":null},{"id":3,"label":"example subpage3","link":"/apx/....","icon":"...","parent":2,"children":null},{"id":4,"label":"example subpage4","link":"/apx/....","icon":"...","parent":2,"children":null}];
for (let i = data.length - 1; i>= 0; i--) {
const entry = data[i];
if (entry.parent > 0) {
const matchingParent = data.find(e => e.id === entry.parent);
if (matchingParent) {
const child = {
label: entry.label,
link: entry.link
};
if (matchingParent.children) {
matchingParent.children.push(child)
} else {
matchingParent.children = [child];
}
data.splice(i, 1);
}
}
}
console.log(data);

get the parent element of the final child or grandchild?

I am working with angularjs and I have this response from my service and Its coming as tree and I am representing that data to the angular tree. this object have 4 main children, name and id. but these children and their children also have children, name and id. What I want is no matter which child I click, I always want to access the value of main root parent name thats "node1" "node2" "node3" as name so i know what is my main parent and whats parent under im clicking.
$scope.loadOrganizationTree = function () {
userManagementFactory.getOrganizationTree()
.success(function(data, status) {
if (JSON.stringify(data.statusType).indexOf("success") > -1) {
$scope.dataForTheTree = data.statusMsg;
}
} else {
$scope.setResponseMsgs(data, status);
}
}).error(function(data, status) {
$scope.userErrorMsg = data.statusMsg;
$scope.showErrorMSg = true ;
});
<div treecontrol class="tree-light accordion-org-tree"
style="height: 609px !important;" tree-model="dataForTheTree"
order-by="name" reverse-order="false" options="treeOptions"
on-selection="showSelected(node, selected, $parentNode, $index, $first, $middle, $last, $odd, $even)"
selected-node="node1" filter-expression="userProfileOrgSearch">
{{node.name}}
</div>
[
{
"id": 1,
"name": "node1",
"children": [
{
"id": 11,
"name": "node1.1",
"children": [
{
"id": 111,
"name": "node1.1.1",
"children": []
}
]
},
{
"id": 12,
"name": "node1.2",
"children": []
}
]
},
{
"id": 2,
"name": "node2",
"children": [
{
"id": 21,
"name": "node2.1",
"nodes": []
},
{
"id": 22,
"name": "node2.2",
"nodes": []
}
]
},
{
"id": 3,
"name": "node3",
"children": [
{
"id": 31,
"name": "node3.1",
"children": []
}
]
}
]
So far I have tried accessing with $parentnode but its not working out for me. Please let me know the solution.
Thanks in advance.
After loading the data you fill $scope.dataForTheTree. Then you can walk through the tree and set the root of each node.
I coded this for you using a var dataForTheTree with your example data.
After running the below, each node has a root property set with it's root node.
var setRoot = function(elementWithChildren, root) {
elementWithChildren.root = root;
if (elementWithChildren.children != null) {
angular.forEach(elementWithChildren.children, function(child) { setRoot(child, root); });
}
};
angular.forEach(dataForTheTree, function(rootNode) { setRoot(rootNode, rootNode); });
we take each node of the tree and call the setRoot() function. which then calls the setRoot function for each of it's children.
Recursive.

Nested json object into single json objects with repeating parent details to construct html table

This is a nested json file and I am trying to arrange it in a readable format to display in a table
I tried to manually put all the keys and values with in a for loop but there should be an elegant way to achieve this and hence I am reaching SO.
The actual JSON is quite a nested one and needed time to execute data with 500k rows
The result should be enhanced JSON with parent values appearing for child values as well
var property = {
"data": [{
"ID": "123456",
"name": "Coleridge st",
"criteria": [
{
"type": "type1",
"name": "name1",
"value": "7",
"properties": []
},
{
"type": "type2",
"name": "name2",
"value": "6",
"properties": [
{
"type": "MAX",
"name": "one",
"value": "100"
}, {
"type": "MIN",
"name": "five",
"value": "5"
}
]
},
{
"type": "type3",
"name": "name3",
"value": "5",
"properties": [{
"type": "MAX1",
"name": "one6",
"value": "1006"
}, {
"type": "MIN2",
"name": "five6",
"value": "56"
}]
}
]
},
{
"ID": "456789",
"name": "New Jersy",
"criteria": [
{
"type": "type4",
"name": "name4",
"value": "6",
"properties": [{
"type": "MAX12",
"name": "one12",
"value": "10012"
}, {
"type": "MIN23",
"name": "five12",
"value": "532"
}]
}
]
}]
};
var output = [];
property.data.forEach(function (users) {
var multirows = {
id: users.ID,
name: users.name,
};
for (var i = 0; i < users.criteria.length; i++) {
var criterias = {
type: users.criteria[i].type,
name: users.criteria[i].name,
value: users.criteria[i].value,
}
var mat_contacts_rows;
if (!isEmpty(users.criteria[i].properties)) {
for (var j = 0; j < users.criteria[i].properties.length; j++) {
var property = {
type: users.criteria[i].properties[j].type,
name: users.criteria[i].properties[j].name,
value: users.criteria[i].properties[j].value
};
mat_contacts_rows = { ...multirows, ...{ criteria: criterias }, ...{ properties: property } };
output.push(mat_contacts_rows);
}
} else {
var property = [];
mat_contacts_rows = { ...multirows, ...{ criteria: criterias }, ...{ properties: property } };
output.push(mat_contacts_rows);
}
}
});
console.log(JSON.stringify(output, undefined, 2))
function isEmpty(obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key))
return false;
}
return true;
}
I think this could be a great exercise to you to don't answer your question but to give you some tips. You should first look at : Lodash wish has a bunch of usefull method to help you doing what you'r trying to do.
In a second time you should avoir using .forEach or for loops and try using Array.prototype.map or Array.prototype.reduce

custom jstree JSON data parse into tree

i have a simple JSON data which is this :
[
"env/child1/env/key1",
"env/child1/key1",
"env/child1/key2",
"env/child1/",
"env/child2/key1",
"env/child2/key2",
"env/child2/",
"env/"
]
how can i make jsTree understands this tree and draw the tree ?
env
child1
key1
key2
do i need to write a custom parsing function or is there a ready way for that.
tree = {
'core' : {
'data' : [
]
}
}
data = [
"env/child1/env/key1",
"env/child1/key1",
"env/child1/key2",
"env/child1/",
"env/child2/key1",
"env/child2/key2",
"env/child2/",
"env/"
];
minlen = -1;
picked = "";
for(i =0; i<data.length; i++) {
if(data[i].length < minlen || minlen == -1) {
minlen = data[i].length;
picked = data[i];
}
}
tree.core.data.push({ "id" : picked, "parent" : "#", "text" : picked })
xdata = data
xdata.splice(xdata.indexOf(picked), 1)
for(i =0; i<xdata.length; i++) {
name = xdata[i]
parent = ""
if(name.substr(name.length-1,1) == '/') {
xname = name.substr(0,name.length-1);
parent = xname.substr(0,xname.lastIndexOf("/")+1)
} else {
parent = name.substr(0,name.lastIndexOf("/")+1)
}
tree.core.data.push({ "id" : name, "parent" : parent, "text" : name })
}
console.log(tree);
I followed the alternative JSON format.
Result:
{
"core": {
"data": [
{
"id": "env/",
"parent": "#",
"text": "env/"
},
{
"id": "env/child1/env/key1",
"parent": "env/child1/env/",
"text": "env/child1/env/key1"
},
{
"id": "env/child1/key1",
"parent": "env/child1/",
"text": "env/child1/key1"
},
{
"id": "env/child1/key2",
"parent": "env/child1/",
"text": "env/child1/key2"
},
{
"id": "env/child1/",
"parent": "env/",
"text": "env/child1/"
},
{
"id": "env/child2/key1",
"parent": "env/child2/",
"text": "env/child2/key1"
},
{
"id": "env/child2/key2",
"parent": "env/child2/",
"text": "env/child2/key2"
},
{
"id": "env/child2/",
"parent": "env/",
"text": "env/child2/"
}
]
}
}
The above data missing parent "env/child1/env/" for child "env/child1/env/key1"
1. correct as follow:
data = [
"env/child1/env/"
"env/child1/env/key1",
"env/child1/key1",
"env/child1/key2",
"env/child1/",
"env/child2/key1",
"env/child2/key2",
"env/child2/",
"env/"
];
The complete code for parent getting the children's values as below:
https://github.com/peterhchen/700-jstree/blob/master/08_PathJSON/0802_PathChild2ParentValueHier.htm

Array with children - display it in the form of parent-child hierarchy

I have the data as shown in step#1 and I would like to rearrange it in the form of parent with corresponding children as in step#2 based on the "id":
step#1:
[
{"id": "card:1.usa", "name": "usa"}, {"id": "card:2", "name": "card2"}, {"id": "card:1", "name": "card1"}, {"id": "card:2.washington", "name": "washington"},
{"id": "card:1.usa.illinios", "name": "illinios"}, {"id": "card:1.usa.illinios.city1", "name": "chicago"}
]
step#2 :
[
{"id": "card:1", "name": "card1", "children": [ {"id": "card:1.usa", "name": "usa", "children":[ {"id": "card:1.usa.illinios", "name": "illinios", "children":[ {"id": "card:1.usa.illinios.city1", "name": "chicago"}] }] } },
{"id": "card:2", "name": "card2", "children": [ {"id": "card:2.washington", "name": "washington" }] }
]
I tried to do the following from my side, but this gets only first level children:
var cardData = [
{"id": "card:1.usa", "name": "usa"}, {"id": "card:2", "name": "card2"}, {"id": "card:1", "name": "card1"}, {"id": "card:2.washington", "name": "washington"},
{"id": "card:1.usa.illinios", "name": "illinios"}, {"id": "card:1.usa.illinios.city1", "name": "chicago"}
]
var subCardList = [];
$scope.parentCard = [];
for(var i=0; i<cardData.length; i++){
if( cardData[i].id.indexOf('.') > -1){
subCardList.push( cardData[i] );
}
}
for(var i=0; i<cardData.length; i++){
for(var j=0; j<subCardList.length; j++){
var cardObj = {};
if( cardData[i].id == subCardList[j].id.substr(0, subCardList[j].id.indexOf('.')) ){ //found matching parent card
cardObj.id = cardData[i].id;
cardObj.children = subCardList[j];
$scope.parentCard.push( cardObj );
}
}
}
Please let me know how I can achieve this through javascript/jquery?
I have improved my solution to make it more comprehensive and also handle the situation where all cards in the hierarchy are not present and in different order.
A sorting is performed on the sub-card array based on id before processing. This ensures order of cards do not break this code.
All top level cards are collected first. Each sub-card is then placed into buckets(Children collection).
I have provided additional data set (cardData1) to highlight boundary conditions.
Please let me know if you need more explanation.
var cardData = [{
"id": "card:1.usa",
"name": "usa"
}, {
"id": "card:2",
"name": "card2"
}, {
"id": "card:1",
"name": "card1"
}, {
"id": "card:2.washington",
"name": "washington"
}, {
"id": "card:1.usa.illinios",
"name": "illinios"
}, {
"id": "card:1.usa.illinios.city1",
"name": "chicago"
}]
var cardData1 = [
{
"id": "card:1.usa.illinios.city1.municipality1",
"name": "DumDum"
},{
"id": "card:1.usa",
"name": "usa"
}, {
"id": "card:2",
"name": "card2"
}, {
"id": "card:1",
"name": "card1"
}, {
"id": "card:2.washington",
"name": "washington"
}, {
"id": "card:1.usa.illinios.city1",
"name": "chicago"
},
]
var subCardList = [];
var subCardMap = {};
var parentCardList = [];
var cardMap = {};
for (var i = 0; i < cardData.length; i++) {
if (cardData[i].id.indexOf('.') > -1) {
subCardList.push(cardData[i]);
subCardMap[cardData[i].id] = cardData[i];
} else {
//top level cards
var cardId = cardData[i].id;
var parentCard = {
id: cardId,
name: cardData[i].name,
children: []
};
cardMap[cardId] = parentCard;
parentCardList.push(parentCard);
}
}
//Sort the subcard list to ensure missing hierarchial cards do not break implementation
subCardList.sort(function (a, b) {
return a.id.toLowerCase().localeCompare(b.id.toLowerCase());
});
//Build buckets(children array) for child cards on the fly
for (var j = 0; j < subCardList.length; j++) {
var topCardId = subCardList[j].id.substr(0, subCardList[j].id.indexOf('.'));
placeInBucket(topCardId, subCardList[j]); //find matching parent card from map
}
function placeInBucket(topCardId, childCard) {
var topCard = cardMap[topCardId]; //get top card
var childIds = childCard.id.split(".");
childIds.splice(0, 1); //Remove the top card id
var childId = "";
var bucket = topCard.children; //Assign the initial bucket as the topcard children array
//loop through all the hierarchy and create complete hierarchy
for (var i = 0; i < childIds.length; i++) {
var key = topCardId + childId + "." + childIds[i];
if (!subCardMap[key]) {
childId += "." + childIds[i];
continue;
} //Do not build hierarchy for missing subcards in the id chain
var child = cardMap[key];
if (!child) {
bucket.push(childCard);
cardMap[key] = childCard; //Register new child to cardMap
break;
}
if (!child.children) child.children = []; //Add Children array to a leaf card if not existing
bucket = child.children;
childId += "." + childIds[i]; //Append the id to drill down the hierarchy
}
}
console.log(JSON.stringify(parentCardList));
The short answer: you need a recursive function, a function that calls itself to nest the children until you find an element that has no children.
So, first, you find all the parents in your list:
[
{"id": "card:2", "name": "card2"},
{"id": "card:1", "name": "card1"},
]
Then, for each of these objects, you run through the array again, finding descendants. For example, for card:1:
[
{"id": "card:1.usa", "name": "usa"},
{"id": "card:1.usa.illinios", "name": "illinios"},
{"id": "card:1.usa.illinios.city1", "name": "chicago"}
]
This is where we'll need recursivity: you need to repeat the same process again, taking this array as your input. So the "parents" you will find are:
[
{"id": "card:1.usa", "name": "usa"}
]
And the children:
[
{"id": "card:1.usa.illinios", "name": "illinios"},
{"id": "card:1.usa.illinios.city1", "name": "chicago"}
]
Since you still have children, you would repeat yet again, until you don't find any more children.
This example might not be as clean as it could be (I wasn't sure how to deal with the ids), but it works:
function nestChildren(list, startIndex){
var parents = [];
// Search list for "parents" --> objects with only one period-separated section
for(var i=0;i<list.length;i++){
var ids = list[i].id.substring(startIndex).split('.'); // Count only sections from parent id
if (ids.length == 1){
parents.push(list[i]);
}
}
for(var i=0;i<parents.length;i++){
var parent = parents[i];
var children = [];
// Search list for "children" --> objects with ids that start with the parent id
for(var j=0;j<list.length;j++){
if (list[j].id.indexOf(parent.id) == 0 && list[j].id != parent.id){
children.push(list[j]);
}
}
if (children.length){
// If there's any children, nest those children too
// Pass the found children as the "list" parameter
// and the parent's id length as the second (to count periods from that index)
parent.children = nestChildren(children, parent.id.length + 1);
}
}
return parents;
}
Working JSFiddle here.

Categories

Resources