Recursively remove 'parent' property based on child's condition - javascript

I have an access JSON object like below
{
"data": [
{
"label": "Self Service",
"data": {
"roles": [
"Employee",
"Manager",
"System Administrator"
]
},
"children": [
{
"label": "Attendance",
"icon": "pi pi-file",
"data": {
"roles": [
"Employee",
"System Administrator"
]
},
"children": [
{
"label": "Clocking",
"icon": "pi pi-file",
"data": {
"roles": [
"Employee",
"System Administrator"
],
"routerLink": ["ESS-ATT-clocking"]
}
},
{
"label": "History",
"icon": "pi pi-file",
"data": {
"roles": [
"Employee",
"System Administrator"
]
}
}
]
},
{
"label": "Claim",
"icon": "pi pi-file",
"data": {
"roles": [
"Manager",
"System Administrator"
]
},
"children": [
{
"label": "Entitlement & Request",
"icon": "pi pi-file",
"data": {
"roles": [
"Manager",
"System Administrator"
]
}
}
]
}
]
},
]
}
stored in a variable accessCtrl. I have another variable
role = "Employee"
Each child node is connected with "children" property.
How can i loop through (recursively) to remove the whole JSON object, "accessCtrl" and remove the particular node, if the "role" is not exists in data.role array?
e.g.
role = "Manager"
the object should return
{
"data": [
{
"label": "Self Service",
"data": {
"roles": [
"Employee",
"Manager",
"System Administrator"
]
},
"children": [
{
"label": "Claim",
"icon": "pi pi-file",
"data": {
"roles": [
"Manager",
"System Administrator"
]
},
"children": [
{
"label": "Entitlement & Request",
"icon": "pi pi-file",
"data": {
"roles": [
"Manager",
"System Administrator"
]
}
}
]
}
]
},
]
}
This is my current code and it doesn't seems work correctly.
function removeNode(obj, parent) {
for (let prop in obj) {
if (
prop === "data" &&
prop.hasOwnProperty("roles") &&
!prop.roles.includes(this.role)
) {
if (parent) {
delete parent.children;
}
} else if (typeof obj[prop] === "object") removeNode(obj[prop], obj);
}
}
removeNode(this.accessCtrl, null);
console.log("this.accessCtrl=", this.accessCtrl);

For a function to be recursive, it needs to call itself.
Please let me know if you need more explanation on how it works.
const input = {
"data": [{
"label": "Self Service",
"data": {
"roles": [
"Employee",
"Manager",
"System Administrator"
]
},
"children": [{
"label": "Attendance",
"icon": "pi pi-file",
"data": {
"roles": [
"Employee",
"System Administrator"
]
},
"children": [{
"label": "Clocking",
"icon": "pi pi-file",
"data": {
"roles": [
"Employee",
"System Administrator"
],
"routerLink": ["ESS-ATT-clocking"]
}
},
{
"label": "History",
"icon": "pi pi-file",
"data": {
"roles": [
"Employee",
"System Administrator"
]
}
}
]
},
{
"label": "Claim",
"icon": "pi pi-file",
"data": {
"roles": [
"Manager",
"System Administrator"
]
},
"children": [{
"label": "Entitlement & Request",
"icon": "pi pi-file",
"data": {
"roles": [
"Manager",
"System Administrator"
]
}
}]
}
]
}]
}
const role = "Manager";
const removeRoles = (tree, role) => {
const newTree = []
for (const item of tree) {
if (item.data.roles.includes(role)) {
if (item.children) {
item.children = removeRoles(item.children, role) // this is where it gets recursive
}
newTree.push(item)
}
}
return newTree;
}
const result = { data: removeRoles(input.data, role) }
console.log(result);

I would separate out the code that filters your recursive array from the actual code that tests the business requirement (here that's ({data: {roles}}) => roles .includes ('Manager').) Here's one possibility:
const filterDeep = (pred) => (xs) =>
xs .flatMap (x => pred (x)
? [{... x, children: filterDeep (pred) (x .children || [])}]
: []
)
const justManagers = (({data, ...rest}) => ({
...rest,
data: filterDeep (({data: {roles}}) => roles .includes ('Manager')) (data)
}))
const input = {data: [{label: "Self Service", data: {roles: ["Employee", "Manager", "System Administrator"]}, children: [{label: "Attendance", icon: "pi pi-file", data: {roles: ["Employee", "System Administrator"]}, children: [{label: "Clocking", icon: "pi pi-file", data: {roles: ["Employee", "System Administrator"], routerLink: ["ESS-ATT-clocking"]}}, {label: "History", icon: "pi pi-file", data: {roles: ["Employee", "System Administrator"]}}]}, {label: "Claim", icon: "pi pi-file", data: {roles: ["Manager", "System Administrator"]}, children: [{label: "Entitlement & Request", icon: "pi pi-file", data: {roles: ["Manager", "System Administrator"]}}]}]}]}
console .log (
justManagers (input)
)
.as-console-wrapper {max-height: 100% !important; top: 0}
filterDeep does the recursive tree walking and includes only those nodes that match our predicate. justManager does a little juggling of the input structure so that we can focus only on the data property and then calls filterDeep passing it our predicate that tests whether our node has the "Manager" role. It leaves any other properties of our input data intact.

Here is a solution using object-scan. We use object-scan for our data processing, but it does take a moment to wrap your head around. Conceptually the solution is simple: We check "leaf first" and then work up the hierarchy and delete if (1) no children are present and (2) desired role is not present
// const objectScan = require('object-scan');
const input = { data: [{ label: 'Self Service', data: { roles: ['Employee', 'Manager', 'System Administrator'] }, children: [{ label: 'Attendance', icon: 'pi pi-file', data: { roles: ['Employee', 'System Administrator'] }, children: [ { label: 'Clocking', icon: 'pi pi-file', data: { roles: ['Employee', 'System Administrator'], routerLink: ['ESS-ATT-clocking'] } }, { label: 'History', icon: 'pi pi-file', data: { roles: ['Employee', 'System Administrator'] } } ] }, { label: 'Claim', icon: 'pi pi-file', data: { roles: ['Manager', 'System Administrator'] }, children: [ { label: 'Entitlement & Request', icon: 'pi pi-file', data: { roles: ['Manager', 'System Administrator'] } } ] }] }] };
const prune = (role, data) => objectScan(['{data,**.children}[*]'], {
rtn: 'count',
filterFn: ({ value, property, parent }) => {
if (
!value.data.roles.includes(role)
&& (value.children || []).length === 0
) {
parent.splice(property, 1);
return true;
}
return false;
}
})(data);
console.log(prune('Manager', input)); // return number of deletions
// => 3
console.log(input);
/* => { data: [
{
label: 'Self Service',
data: { roles: [ 'Employee', 'Manager', 'System Administrator' ] },
children: [
{ label: 'Claim', icon: 'pi pi-file', data: { roles: [ 'Manager', 'System Administrator' ] }, children: [
{ label: 'Entitlement & Request', icon: 'pi pi-file', data: { roles: [ 'Manager', 'System Administrator' ] } }
] }
]
}
] } */
.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

Related

nest items in JSON based on value?

Trying to get my head around this one..
Incoming data looks like:
[
{
"value": {
"label": "MZ Algal bloom",
"type": "case",
"incident": {
"name": "Algal bloom"
},
"personName": "Lionel Carter"
}
},
{
"value": {
"label": "BW Algal bloom",
"type": "case",
"incident": {
"name": "Algal bloom"
},
"personName": "Jerome Yost"
}
},
{
"value": {
"label": "Detergent",
"type": "case",
"incident": null,
"personName": "Jerald Legros"
}
}
]
I would like to transform this into
[
{
"label": "Algal bloom",
"children": [
{ "label": "Lionel Carter", "type": "case"},
{ "label": "Jerome Yost", "type": "case" }]
},
{ "label": "Detergent", "type": "case" }
]
Basically, the rule is that if incident is not NULL then the incident name becomes the parent and the children hold the personName - otherwise we simply pass through the label and type. I can walk the array and switch out the label with the incident name, but I'm not sure how to group up the incidents..
It's basic grouping with an exception for elements without incident.
You can group the elements without incident in a separate group:
const data = [{"value": {"label": "MZ Algal bloom","type": "case","incident": {"name": "Algal bloom"},"personName": "Lionel Carter"}},{"value": {"label": "BW Algal bloom","type": "case","incident": {"name": "Algal bloom"},"personName": "Jerome Yost"}},{"value": {"label": "Detergent","type": "case","incident": null,"personName": "Jerald Legros"}}];
function group(data) {
const result = data.reduce((acc, { value }) => {
if (!value.incident) {
acc.ungrouped.push({ label: value.label, type: value.type });
} else {
if (!acc.groups[value.incident.name]) acc.groups[value.incident.name] = { label: value.incident.name, children: [] };
acc.groups[value.incident.name].children.push({ label: value.personName, type: value.type });
}
return acc;
}, { groups: {}, ungrouped: [] });
return [...Object.values(result.groups), ...result.ungrouped];
}
console.log(group(data));

How do I loop through nested arrays?

I have this structure below, and I want to loop through the hierarchy without missing any object.
{
"countries": [
{
"name": "Denmark",
"id": "APA1",
"children": [
{
"name": "Zealand",
"id": "APA1.1",
"parentId": "APA1",
"children": [
{
"name": "Copenhagen",
"id": "APA1.1.1",
"parentId": "APA1.1",
"children": [
{
"name": "Dublin",
"id": "ANA1",
"parentId": "APA1.1.1.1",
"hostNames": [
{
"ip": "20.190.129.1"
},
{
"ip": "20.190.129.2"
}
]
}
]
}
]
},
{
"name": "Jutland",
"id": "APA1.2",
"parentId": "APA1",
"children": [
{
"name": "Nordjylland",
"id": "APA1.2.1",
"parentId": "APA1.2",
"children": [
{
"name": "Aalborg",
"id": "APA1.2.1.1",
"parentId": "APA1.2.1",
"children": [
{
"name": "Risskov",
"id": "ANA3",
"parentId": "APA1.2.1.1",
"hostNames": [
{
"ip": "40.101.81.146"
}
]
},
{
"name": "Brabrand",
"id": "ANA4",
"parentId": "APA1.2.1.1",
"hostNames": [
{
"ip": "43.203.94.182"
}
]
}
]
}
]
}
]
}
]
}
]
}
The reason why I want to loop through the hierarchy is that I want to turn this into a flat structure. So essentially I'm gonna take every object and move it to another array which has the structure that I want. I just want to know how to access the children.
The wanted result:
"applicationGroups": [
{
"id" : "APA1",
"name": "Denmark",
},
{
"name": "Zealand",
"id": "APA1.1",
"parentId": "APA1"
},
{
"name": "Copenhagen",
"id": "APA1.1.1",
"parentId": "APA1.1"
},
{
"name": "Dublin",
"id": "ANA1",
"parentId": "APA1.1.1.1"
},
{
"name": "Jutland",
"id": "APA1.2",
"parentId": "APA1"
},
{
"name": "Nordjylland",
"id": "APA1.2.1",
"parentId": "APA1.2"
},
{
"name": "Aalborg",
"id": "APA1.2.1.1",
"parentId": "APA1.2.1"
},
{
"name": "Risskov",
"id": "ANA3",
"parentId": "APA1.2.1.1"
},
{
"name": "Brabrand",
"id": "ANA4",
"parentId": "APA1.2.1.1"
}
]
I'm a bit new to JavaScript, and I don't really know where to start, but this example that I have given is not identical to the actual one that I'm working on, so I just want the principle so I can implement it myself in my actual code.
You could take a flatMap approach for the recursive call of a flattening callback.
const
flat = ({ hostNames, children = [], ...o }) => [o, ...children.flatMap(flat)],
data = { countries: [{ name: "Denmark", id: "APA1", children: [{ name: "Zealand", id: "APA1.1", parentId: "APA1", children: [{ name: "Copenhagen", id: "APA1.1.1", parentId: "APA1.1", children: [{ name: "Dublin", id: "ANA1", parentId: "APA1.1.1.1", hostNames: [{ ip: "20.190.129.1" }, { ip: "20.190.129.2" }] }] }] }, { name: "Jutland", id: "APA1.2", parentId: "APA1", children: [{ name: "Nordjylland", id: "APA1.2.1", parentId: "APA1.2", children: [{ name: "Aalborg", id: "APA1.2.1.1", parentId: "APA1.2.1", children: [{ name: "Risskov", id: "ANA3", parentId: "APA1.2.1.1", hostNames: [{ ip: "40.101.81.146" }] }, { name: "Brabrand", id: "ANA4", parentId: "APA1.2.1.1", hostNames: [{ ip: "43.203.94.182" }] }] }] }] }] }] },
result = data.countries.flatMap(flat);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can combine the Array.flat() method and this answer to flatten objects recursively.
Using recursive functions is the faster way to accomplish that.
To get a flat structure you could use reduce method to create recursive function.
const data = {"countries":[{"name":"Denmark","id":"APA1","children":[{"name":"Zealand","id":"APA1.1","parentId":"APA1","children":[{"name":"Copenhagen","id":"APA1.1.1","parentId":"APA1.1","children":[{"name":"Dublin","id":"ANA1","parentId":"APA1.1.1.1","hostNames":[{"ip":"20.190.129.1"},{"ip":"20.190.129.2"}]}]}]},{"name":"Jutland","id":"APA1.2","parentId":"APA1","children":[{"name":"Nordjylland","id":"APA1.2.1","parentId":"APA1.2","children":[{"name":"Aalborg","id":"APA1.2.1.1","parentId":"APA1.2.1","children":[{"name":"Risskov","id":"ANA3","parentId":"APA1.2.1.1","hostNames":[{"ip":"40.101.81.146"}]},{"name":"Brabrand","id":"ANA4","parentId":"APA1.2.1.1","hostNames":[{"ip":"43.203.94.182"}]}]}]}]}]}]}
function flat(data) {
return data.reduce((r, { children, ...rest }) => {
if (children) r.push(...flat(children))
r.push(rest)
return r;
}, [])
}
const result = flat(data.countries)
console.log(result)

angular-tree-component json to acceptable format and dynamically create checkbox or radio button

I have a treeview using angular-tree-component as use of following link Refernece
Where the array is in below format:
nodes = [
{
id: 1,
name: 'root1',
children: [
{ id: 2, name: 'child1' },
{ id: 3, name: 'child2' }
]
},
{
id: 4,
name: 'root2',
children: [
{ id: 5, name: 'child2.1' },
{
id: 6,
name: 'child2.2',
children: [
{ id: 7, name: 'granchild 2.2.1' }
]
}
]
}
];
But I have a json nested array in the below format:
[
{
"name": "root1",
"subCategory": [
{
"name": "child1",
"childCategory": []
},
{
"name": "child2",
"childCategory": []
}
]
},
{
"name": "level2",
"subCategory": [
{
"name": "level2.1",
"childCategory": []
},
{
"name": "level2.2",
"childCategory": [
{
"name": "granchild 2.2.1",
"type": "checkbox"
}
]
}
]
}
]
My doubts are below:
How can I convert json to required array Format which is acceptable by the angular-tree-component
As you can see the type is checkbox {"name": "granchild 2.2.1","type": "checkbox"}. So the grandchild 2.2.1 should be a check box and if it was radio button it should be radio.
Please guide me...
You can transform json to your array using this code (id=1 at start)
a.map(x=> (
x.id=id,
id++,
x.subCategory.map(y=> (
y.id=id,
id++,
(y.childCategory.length ? y.children=y.childCategory : 0),
y.childCategory.map(z=> (z.id=id, id++)),
delete y.childCategory
)),
(x.subCategory.length ? x.children=x.subCategory : 0),
delete x.subCategory
));
let a=[
{
"name": "root1",
"subCategory": [
{
"name": "child1",
"childCategory": []
},
{
"name": "child2",
"childCategory": []
}
]
},
{
"name": "level2",
"subCategory": [
{
"name": "level2.1",
"childCategory": []
},
{
"name": "level2.2",
"childCategory": [
{
"name": "granchild 2.2.1",
"type": "checkbox"
}
]
}
]
}
]
let id=1;
a.map(x=> (
x.id=id,
id++,
x.subCategory.map(y=> (
y.id=id,
id++,
(y.childCategory.length ? y.children=y.childCategory : 0),
y.childCategory.map(z=> (z.id=id, id++)),
delete y.childCategory
)),
(x.subCategory.length ? x.children=x.subCategory : 0),
delete x.subCategory
));
console.log(a);

push elements of each object inside each object inside another array

I have two arrays, one is my original one called data which consists of :
const datas = [
{
name: 'core Test',
item: [
{
name: 'test/core/core.js',
item: "item1"
}
]
},
{
name: 'users Test',
item: [
{
name: 'test/users/user.js',
item: "item2"
}
]
}
]
And i have another array called replace, which i'm trying to push each of its elements inside my original one, inside the
const replace = [
{
type: "test1",
number: "1",
},
{
type: "test2",
number: "2",
}
]
Here is my code :
const transformedData = datas.map(data => {
data.item = data.item.map(x => ({
name: x.name,
type: replace.map(y=>{return y;})
}))
return data
})
The output i get :
[
{
"name": "core Test",
"item": [
{
"name": "test/core/core.js",
"type": [
{ "type": "test1", "number": "1" },
{ "type": "test2", "number": "2" }
]
}
]
},
{
"name": "users Test",
"item": [
{
"name": "test/users/user.js",
"type": [
{ "type": "test1", "number": "1" },
{ "type": "test2", "number": "2" }
]
}
]
}
]
The output i want :
[
{
"name": "core Test",
"item": [
{
"name": "test/core/core.js",
"type": { "type": "test1", "number": "1" }
}
]
},
{
"name": "users Test",
"item": [
{
"name": "test/users/user.js",
"type": { "type": "test2", "number": "2" }
}
]
}
]
This is because you're mapping all the way through the replace array every single time for each time you're inside of a value inside of datas. Instead you want to keep track of the index with your original map so then you only have one instance each time.
Try something like:
const transformedData = datas.map((data, index) => {
data.item = data.item.map(x => ({
name: x.name,
type: replace[index]
}))
return data;
});

How to group similar sub-items of JSON

I want to group similar sub-item of my Json into one and then merge all their sub-item under that created item.
this is my json file:
{
"car": [
{
"name": "benz",
"details": [
{
"name": "C1",
"year": [
{
"name": "1850",
"errs": [
{
"user": "model-A",
"text": "error text on model-H"
},
{
"user": "model-C",
"text": "error text on model-C"
}
]
},
{
"name": "1820",
"errs": [
{
"user": "model-C",
"text": "error text on model-C"
}
]
}
]
}
]
},
{
"name": "vw",
"details": [
{
"name": "A1",
"year": [
{
"name": "1860",
"errs": []
},
{
"name": "1870",
"errs": [
{
"user": "model-A",
"text": "error text on model-H"
}
]
}
]
},
{
"name": "A2",
"year": [
{
"name": "1910",
"errs": []
},
{
"name": "1950",
"errs": [
{
"user": "model-A",
"text": "error text on model-H"
}
]
}
]
}
]
}
]
}
my tree structure is based on label and children, so my parent item is label and sub tree is children.
and this is my ts file:
ngOnInit():void {
this.http.get('.car.json').subscribe((data) => {
data.car.forEach((carItem) => {
carItem.details.forEach((detail) => {
const errorsTree = {};
detail.year.forEach((year) => {
year.errs.forEach((err) => {
let userNode;
let carNode;
let detailNode;
if (errorsTree[err.userAgent]) {
userNode = errorsTree[err.userAgent];
} else {
userNode = {name: err.userAgent, cars: {}};
errorsTree[err.userAgent] = userNode;
}
const components = userNode.cars;
if (components[carItem.name]) {
carNode = cars[carItem.name];
} else {
carNode = {name: carItem.name, details: {}};
components[carItem.name] = carNode;
}
const detailsItems = carNode.details;
if (detailsItems[detail.name]) {
detailNode = detailsItems[detail.name];
} else {
detailNode = {name: detail.name, tests: {}};
detailsItems[detail.name] = detailNode;
}
detailNode.tests[test.name] = test;
this.TreeModel.push({
label: userNode.name,
children: _.values(userNode.cars).map((car) => {
return {
label: car.name,
children: _.values(car.details).map((deta) => {
return {
label: deta.name,
children: _.values(deta.tests).map((testItem) => {
return {
label: testItem.name,
err: err.text
};
})
};
})
};
})
});
});
});
});
});
});
}
after running the code the tree will be like this:
model-A
benz
C1
model-C
benz
C1
model-C
benz
C1
model-A
vw
A1
model-A
vw
A2
but it is not correct, the result should be like following:
[
{
label: 'model-A',
children: [
{
label: 'benz',
children: [
{
label: 'C1'
}
]
},
{
label: 'vw',
children: [
{
label: 'A1'
},
{
label: 'A2'
}
]
}
]
},
{
label: 'model-C',
children: [
{
label: 'benz',
children: [
{
label: 'C1'
}
]
}
]
}
]
do you have i idea how to group this Json like the above structure with Angular 5
thanks.
You can use something like this -
var processedJSON = {};
const data = {
"car": [{
"name": "benz",
"details": [{
"name": "C1",
"year": [{
"name": "1850",
"errs": [{
"user": "model-A",
"text": "error text on model-H"
},
{
"user": "model-C",
"text": "error text on model-C"
}
]
},
{
"name": "1820",
"errs": [{
"user": "model-C",
"text": "error text on model-C"
}]
}
]
}]
},
{
"name": "vw",
"details": [{
"name": "A1",
"year": [{
"name": "1860",
"errs": []
},
{
"name": "1870",
"errs": [{
"user": "model-A",
"text": "error text on model-H"
}]
}
]
},
{
"name": "A2",
"year": [{
"name": "1910",
"errs": []
},
{
"name": "1950",
"errs": [{
"user": "model-A",
"text": "error text on model-H"
}]
}
]
}
]
}
]
};
data.car.forEach(function(car, i) {
car.details.forEach(function(detail, j) {
detail.year.forEach(function(year, k) {
year.errs.forEach(function(element, l) {
if (!processedJSON[element.user]) {
processedJSON[element.user] = {};
}
if (!processedJSON[element.user][car.name]) {
processedJSON[element.user][car.name] = [];
}
if (processedJSON[element.user][car.name].indexOf(detail.name) == -1) {
processedJSON[element.user][car.name].push(detail.name);
}
});
});
});
});
console.log(processedJSON);
You can structure your data using array#reduce with multiple array#forEach, then from this result, use array#map with Object.keys() to get the final format.
const data = { "car": [ { "name": "benz", "details": [ { "name": "C1", "year": [ { "name": "1850", "errs": [ { "user": "model-A", "text": "error text on model-H" }, { "user": "model-C", "text": "error text on model-C" } ] }, { "name": "1820", "errs": [ { "user": "model-C","text": "error text on model-C" } ] } ] } ] }, { "name": "vw", "details": [ { "name": "A1", "year": [ { "name": "1860", "errs": [] }, { "name": "1870", "errs": [ { "user": "model-A", "text": "error text on model-H" } ] } ] }, { "name": "A2", "year": [{ "name": "1910", "errs": [] }, { "name": "1950", "errs": [ { "user": "model-A", "text": "error text on model-H" } ] } ] } ] } ] },
result = data.car.reduce((r, {name, details}) => {
details.forEach(o1 => {
o1.year.forEach(o2 => {
o2.errs.forEach(({user, text}) => {
r[user] = r[user] || {};
r[user][name] = r[user][name] || [];
if(!r[user][name].includes(o1.name))
r[user][name].push(o1.name);
});
});
});
return r;
},{});
const output = Object.keys(result).map(k => ({label : k, children : Object.keys(result[k]).map(key => ({label: key, children : result[k][key].map(([label]) => ({label}) ) }) ) }) );
console.log(output);

Categories

Resources