Create a nested JSON based on an array of JSON objects - javascript

I have an array of JSON objects which is something like:
var fileArray = [
{ name: "file1", path: "/main/file1" },
{ name: "file2", path: "/main/folder2/file2" },
{ name: "file4", path: "/main/folder3/file4" },
{ name: "file5", path: "/main/file5" },
{ name: "file6", path: "/main/file6" }
];
What I want it to look like eventually is:
fileTree = [
{
"name": "file1",
"children": []
},
{
"name": "folder1"
"children": [
{
"name": "folder2",
"children": [
{
"name": "file2",
"children": []
}
]
},
{
"name": "folder3",
"children": [
{
"name": "file4",
"children": []
}
]
}
]
},
{
"name": "file5",
"children": []
},
{
"name": "file6",
"children": []
}
];
I tried the solution mentioned in Create a nested UL menu based on the URL path structure of menu items but the first comment to the first answer is exactly the problem I am having. All help is appreciated.

You could use a nested hash table as reference to the same directories and build in the same way the result set.
var fileArray = [{ name: "file1", path: "/main/file1" }, { name: "file2", path: "/main/folder2/file2" }, { name: "file4", path: "/main/folder3/file4" }, { name: "file5", path: "/main/file5" }, { name: "file6", path: "/main/file6" }],
temp = [],
fileTree;
fileArray.forEach(function (hash) {
return function (a) {
a.path.replace('/', '').split('/').reduce(function (r, k) {
if (!r[k]) {
r[k] = { _: [] };
r._.push({ name: k, children: r[k]._ });
}
return r[k];
}, hash);
};
}({ _: temp }));
fileTree = temp[0].children;
console.log(fileTree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

This won't give the exact result you want but I think it's usable:
var fileArray = [
{ name: "file1", path: "/main/file1" },
{ name: "file2", path: "/main/folder2/file2" },
{ name: "file4", path: "/main/folder3/file4" },
{ name: "file5", path: "/main/file5" },
{ name: "file6", path: "/main/file6" }
];
var tree = fileArray.reduce(function (b, e) {
var pathbits = e.path.split("/");
var r = b;
for (var i=1; i < pathbits.length - 1; i++) {
bit = pathbits[i];
if (!r[bit]) r[bit] = {};
r = r[bit];
}
if (!r.files) r.files = [];
r.files.push(e.name);
return b;
}, {});
console.log(tree);

Related

Build a Tree from Flat Array

This is a list that is returned by API and need to convert this to a Tree which can display so a user can select the required permissions.
const list = [
{
resource: 'User',
action: 'Create',
id: 1,
},
{
resource: 'User',
action: 'Edit',
id: 2,
},
{
resource: 'User',
action: 'Delete',
id: 3,
},
{
resource: 'Utility.Rule',
action: 'Create',
id: 4,
},
{
resource: 'Utility.Rule',
action: 'Edit',
id: 5,
},
{
resource: 'Utility.Config',
action: 'Create',
id: 6,
},
];
And need to get converted to a Tree
{
"id": "root",
"name": "MyTree",
"children": [
{
"id": "User",
"name": "User",
"children": [
{
"id": "1",
"name": "Create"
},
{
"id": "2",
"name": "Edit"
},
{
"id": "3",
"name": "Delete"
}
]
},
{
"id": "Utility",
"name": "Utility",
"children": [
{
"id": "Rule",
"name": "Rule",
"Children": [
{
"id": "4",
"name": "Create"
},
{
"id": "5",
"name": "Edit"
}
]
},
{
"id": "Config",
"name": "Config",
"children": [
{
"id": "6",
"name": "Create"
}
]
}
]
}
]
}
I have tried different methods like Map and reduce but not getting desired output.
const arrayToTree = (arr, parent = 'User') =>
arr.filter(item => item.parent === parent)
.map(child => ({ ...child, children: arrayToTree(arr,
child.index) }));
arrayToTree(list, 'User')
//This gives me stackoverflow error.
Other option I tried was to build first the map
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;
}
It would be great if I can get some hit how to construct the main node and then add the children.
The solution I came up with was to loop through the list, splitting each resource by '.' and adding a node for each resource directory if not already within the parent node's children
const list = [
{ resource: 'User', action: 'Create', id: 1 },
{ resource: 'User', action: 'Edit', id: 2 },
{ resource: 'User', action: 'Delete', id: 3 },
{ resource: 'Utility.Rule', action: 'Create', id: 4 },
{ resource: 'Utility.Rule', action: 'Edit', id: 5 },
{ resource: 'Utility.Config', action: 'Create', id: 6 },
];
function arrayToTree(list, rootNode = { id: 'root', name: 'MyTree' }) {
const addChild = (node, child) => ((node.children ?? (node.children = [])).push(child), child);
for (const { id, action, resource } of list) {
const path = resource.split('.');
const node = path.reduce(
(currentNode, targetNodeId) =>
currentNode.children?.find(({ id }) => id === targetNodeId) ??
addChild(currentNode, { id: targetNodeId, name: targetNodeId }),
rootNode
);
addChild(node, { id: String(id), name: action });
}
return rootNode;
}
console.log(arrayToTree(list));

Javascript Recurse JSON array with children objects

Is there a way to recurse the following JSON without for-looping the nested children?
My recursive function must be missing a case as it's not returning everything.
iterateTree(node, children) {
console.log(node.name)
if(node.children.length == 0) {
return;
}
for(var i = 0; i < children.length; i++) {
var child_node = children[i];
return this.iterateTree(child_node, child_node.children);
}
}
for(var i = 0; i < sample.length; i++) {
var node = sample[i];
this.iterateTree(node, node.children);
}
var sample = [
{
"name": "hello world",
"children": [
{
"name": "fruits",
"children": []
},
{
"name": "vegetables",
"children": []
},
{
"name": "meats",
"children": [
{
"name": "pork",
"children": []
},
{
"name": "beef",
"children": []
},
{
"name": "chicken",
"children": [
{
"name": "organic",
"children": []
},
{
"name": "farm raised",
"children": []
}
]
},
]
}
]
},
{
"name": "second folder",
"children": []
},
{
"name": "third folder",
"children": [
{
"name": "breads",
"children": []
},
{
"name": "coffee",
"children": [
{
"name": "latte",
"children": []
},
{
"name": "cappucino",
"children": []
},
{
"name": "mocha",
"children": []
},
]
},
]
}
]
Aiming to achieve the following output (similiar to file structure)
hello world
-fruits
-vegetables
-meats
--pork
--beef
--chicken
---organic
---farm raised
second folder
third folder
-breads
-coffee
--latte
--cappucino
--mocha
You could create recursive function using reduce method to iterate through your nested data structure, return array and then use join method on that array.
var sample = [{"name":"hello world","children":[{"name":"fruits","children":[]},{"name":"vegetables","children":[]},{"name":"meats","children":[{"name":"pork","children":[]},{"name":"beef","children":[]},{"name":"chicken","children":[{"name":"organic","children":[]},{"name":"farm raised","children":[]}]}]}]},{"name":"second folder","children":[]},{"name":"third folder","children":[{"name":"breads","children":[]},{"name":"coffee","children":[{"name":"latte","children":[]},{"name":"cappucino","children":[]},{"name":"mocha","children":[]}]}]}]
function tree(data, prev = '') {
return data.reduce((r, e) => {
r.push(prev + e.name)
if (e.children.length) r.push(...tree(e.children, prev + '-'));
return r;
}, [])
}
const result = tree(sample).join('\n')
console.log(result)
To create same structure in HTML you could use forEach method instead.
var sample = [{"name":"hello world","children":[{"name":"fruits","children":[]},{"name":"vegetables","children":[]},{"name":"meats","children":[{"name":"pork","children":[]},{"name":"beef","children":[]},{"name":"chicken","children":[{"name":"organic","children":[]},{"name":"farm raised","children":[]}]}]}]},{"name":"second folder","children":[]},{"name":"third folder","children":[{"name":"breads","children":[]},{"name":"coffee","children":[{"name":"latte","children":[]},{"name":"cappucino","children":[]},{"name":"mocha","children":[]}]}]}]
function tree(data, parent) {
const ul = document.createElement('ul');
data.forEach(el => {
const li = document.createElement('li');
li.textContent = el.name;
ul.appendChild(li);
if (el.children.length) {
tree(el.children, li)
}
})
parent.appendChild(ul)
}
const parent = document.getElementById('root')
tree(sample, parent)
<div id="root"></div>
var sample = [{"name":"hello world","children":[{"name":"fruits","children":[]},{"name":"vegetables","children":[]},{"name":"meats","children":[{"name":"pork","children":[]},{"name":"beef","children":[]},{"name":"chicken","children":[{"name":"organic","children":[]},{"name":"farm raised","children":[]}]}]}]},{"name":"second folder","children":[]},{"name":"third folder","children":[{"name":"breads","children":[]},{"name":"coffee","children":[{"name":"latte","children":[]},{"name":"cappucino","children":[]},{"name":"mocha","children":[]}]}]}]
level = 0;
var hyphens = '';
function recursive_loop(s) {
console.log(hyphens + s.name);
var c = s.children;
if (c.length) hyphens += '-';
var empty = false;
for (let i = 0; i < c.length; i++) {
if (c[i].children) {
recursive_loop(c[i]);
}
if (c[i].children.length)
empty = true;
}
if (empty) hyphens = '';
}
for (let i = 0; i < sample.length; i++) {
recursive_loop(sample[i]);
}
We use object-scan foe many data processing / traversal tasks. It's powerful once you wrap your head around it. Here is how you could solve your question
// const objectScan = require('object-scan');
const display = (input) => objectScan(['**'], {
reverse: false,
rtn: 'entry',
filterFn: ({ value }) => typeof value === 'string'
})(input)
.map(([k, v]) => `${'-'.repeat(k.length / 2 - 1)}${v}`);
const sample = [{ name: 'hello world', children: [{ name: 'fruits', children: [] }, { name: 'vegetables', children: [] }, { name: 'meats', children: [{ name: 'pork', children: [] }, { name: 'beef', children: [] }, { name: 'chicken', children: [{ name: 'organic', children: [] }, { name: 'farm raised', children: [] }] }] }] }, { name: 'second folder', children: [] }, { name: 'third folder', children: [{ name: 'breads', children: [] }, { name: 'coffee', children: [{ name: 'latte', children: [] }, { name: 'cappucino', children: [] }, { name: 'mocha', children: [] }] }] }];
const result = display(sample);
result.forEach((l) => console.log(l));
// => hello world
// => -fruits
// => -vegetables
// => -meats
// => --pork
// => --beef
// => --chicken
// => ---organic
// => ---farm raised
// => second folder
// => third folder
// => -breads
// => -coffee
// => --latte
// => --cappucino
// => --mocha
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

looping through a json tree object and create new array

Is there a way good way JS/ES6 to loop through an object and it's children and creating new object tree array.
I have this json tree object:
[
{
id: "001",
deparmentsIds: [
"002",
"003"
],
details: {
parentDeparmentsId: null,
name: "Top"
}
},
{
id: "002",
deparmentsIds:[
"004"
],
details: {
parentDeparmentsId: ["001"],
name: "Operations"
}
},
{
id: "003",
deparmentsIds:[]
details: {
parentDeparmentsId: ["001"],
name: "Support"
}
},
{
id: "004",
deparmentsIds:[]
details: {
parentDeparmentsId: ["002"],
name: "Support operations"
}
}
]
I want to create new object array tree that looks like this:
You could create recursive function with reduce and map method to create nested object structure.
const data = [{"id":"001","deparmentsIds":["002","003"],"details":{"parentDeparmentsId":null,"name":"Top"}},{"id":"002","deparmentsIds":["004"],"details":{"parentDeparmentsId":"001","name":"Operations"}},{"id":"003","deparmentsIds":[],"details":{"parentDeparmentsId":"001","name":"Support"}},{"id":"004","deparmentsIds":[],"details":{"parentDeparmentsId":"002","name":"Support operations"}}]
function tree(input, parentId) {
return input.reduce((r, e) => {
if (e.id == parentId || parentId == undefined && e.details.parentDeparmentsId == null) {
const children = [].concat(...e.deparmentsIds.map(id => tree(input, id)))
const obj = {
[e.details.name]: children
}
r.push(obj)
}
return r;
}, [])
}
const result = tree(data)
console.log(result)
You could collect all information in an object with a single loop and return only the nodes with no parent.
function getTree(data, root) {
var o = {};
data.forEach(({ id, details: { parentDeparmentsId: parent, name } }) => {
var temp = { id, name };
if (o[id] && o[id].children) {
temp.children = o[id].children;
}
o[id] = temp;
o[parent] = o[parent] || {};
o[parent].children = o[parent].children || [];
o[parent].children.push(temp);
});
return o[root].children;
}
var data = [{ id: "001", deparmentsIds: ["002", "003"], details: { parentDeparmentsId: null, name: "Top" } }, { id: "002", deparmentsIds: ["004"], details: { parentDeparmentsId: ["001"], name: "Operations" } }, { id: "003", deparmentsIds: [], details: { parentDeparmentsId: ["001"], name: "Support" } }, { id: "004", deparmentsIds: [], details: { parentDeparmentsId: ["002"], name: "Support operations" } }],
tree = getTree(data, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Create an array based on two arrays

I have two arrays details and options that coming from different sources (web api requests)
details: [
{ id: 'groups', option: true },
{ id: 'category', option: false }
]
options: {
groups: [
{ id: 'g1' },
{ id: 'g2' }
],
category: [
{ id: 'c1' },
{ id: 'c2' }
],
other: [
{ id: 'o1' },
{ id: 'o2' }
],
}
I want to combine these tow arrays like
combined: [
groups:
{
options:[
{ id: 'g1' },
{ id: 'g2' }
],
details: { option: true}
},
category:
{
options: [
{ id: 'c1' },
{ id: 'c2' }
],
details: { option: false}
},
]
Basically if any id from details is matching to options property it should go in to new array under the same property name and all details except id goes to related details property.
What is the best way of doing that? Is lodash can handle that ?
If you only want the items in both options and details (intersection):
var details = [
{ id: 'groups', option: true },
{ id: 'category', option: false }
]
var options = {
groups: [
{ id: 'g1' },
{ id: 'g2' }
],
category: [
{ id: 'c1' },
{ id: 'c2' }
],
other: [
{ id: 'o1' },
{ id: 'o2' }
]
}
var combined = {};
details.forEach(({id: id, option: option}) => {
if (options[id]) {
combined[id] = combined[id] || {};
combined[id].options = options[id];
combined[id].details = {option: option};
}
})
console.log(JSON.stringify(combined, null, "\t"))
/*
{
"groups": {
"options": [
{
"id": "g1"
},
{
"id": "g2"
}
],
"details": {
"option": true
}
},
"category": {
"options": [
{
"id": "c1"
},
{
"id": "c2"
}
],
"details": {
"option": false
}
}
}
*/
If you want to retain all items from options and details whether or not they match (union):
var details = [
{ id: 'groups', option: true },
{ id: 'category', option: false }
]
var options = {
groups: [
{ id: 'g1' },
{ id: 'g2' }
],
category: [
{ id: 'c1' },
{ id: 'c2' }
],
other: [
{ id: 'o1' },
{ id: 'o2' }
]
}
var combined = {};
Object.keys(options).forEach(id => {
combined[id] = {};
combined[id].options = options[id];
})
details.forEach(({id: id, option: option}) => {
combined[id] = combined[id] || {};
combined[id].details = {option: option};
})
console.log(JSON.stringify(combined, null, "\t"))
/*
{
"groups": {
"options": [
{
"id": "g1"
},
{
"id": "g2"
}
],
"details": {
"option": true
}
},
"category": {
"options": [
{
"id": "c1"
},
{
"id": "c2"
}
],
"details": {
"option": false
}
},
"other": {
"options": [
{
"id": "o1"
},
{
"id": "o2"
}
]
}
}
*/
You need to use [] notation to push details values.
options['groups']['details'] = true
options['groups']['category'] = false
Here's the solution for your problem
var details= [
{ id: 'groups', option: true },
{ id: 'category', option: false }
];
var options= {
groups: [
{ id: 'g1' },
{ id: 'g2' }
],
category: [
{ id: 'c1' },
{ id: 'c2' }
],
other: [
{ id: 'o1' },
{ id: 'o2' }
],
};
var combined = {};
for (var i= 0;i<details.length;i++){
var obj = {}
obj.options=options[details[i].id];
obj.details = {};
obj.details.option=details[i].option;
combined[details[i].id] = obj;
}
console.log(combined)

AngularJS find object in first array which is not presented in second

Let's say i have such two objects:
first:
[
{
id: "123",
title: "123",
options: []
},
{
id: "456",
title: "456",
options: [
{
id: "0123",
title: "0123",
options: []
}
]
},
{
id: "789",
title: "789",
options: []
},
]
and second
[
{
id: "123",
title: "123",
options: []
},
{
id: "789",
title: "789",
options: []
},
]
as you could see in second array i'm missing this part:
{
id: "456",
title: "456",
options: [
{
id: "0123",
title: "0123",
options: []
}
]
}
how it would be right and better to iterate and find missing elements in angular?
You can do it like
<div ng-app>
<div ng-controller="MyCtrl">{{availableGroups}}
</div>
</div>
js code
function MyCtrl ($scope) {
$scope.groups = [
{
id: "123",
title: "123",
options: []
},
{
id: "456",
title: "456",
options: [
{
id: "0123",
title: "0123",
options: []
}
]
},
{
id: "789",
title: "789",
options: []
},
];
$scope.assignedGroups = [
{
id: "123",
title: "123",
options: []
},
{
id: "789",
title: "789",
options: []
},
];
$scope.availableGroups = (function () {
var assignedGroupsIds = {};
var groupsIds = {};
var result = [];
$scope.assignedGroups.forEach(function (el, i) {
assignedGroupsIds[el.id] = $scope.assignedGroups[i];
});
$scope.groups.forEach(function (el, i) {
groupsIds[el.id] = $scope.groups[i];
});
for (var i in groupsIds) {
if (!assignedGroupsIds.hasOwnProperty(i)) {
result.push(groupsIds[i]);
}
}
return result;
}());
}
Here is working jsFiddle
Thanks
Let's say that the first array is named first and the second second. Now sort them first:
function comp(a, b){
if(a.id < b.id) return -1;
if(a.id > b.id) return 1;
return 0;
}
first.sort(comp);
second.sort(comp);
Then iterate through them to find missing elements:
var missing = {};
for(var i = 0, j = 0; i < first.length; ++i){
if(first[i].id == second[j].id){
j++;
continue;
}
missing.push(first[i]);
}
The missing array now contains objects that is in the first array but not the second one.
Note that I didn't use AngularJS; it's plain Javascript.

Categories