How to create a tree from flat array? - javascript

There is a flat array with id, parentid links. The array example is:
let e = [
{
"id": 1,
"descr": "Те",
"moddate": "2023-02-09T13:37:46.366961",
"isgroup": true
}, {
"id": 2,
"descr": "Те",
"moddate": "2023-02-09T13:37:46.366961",
"parentid": 1
}];
I have tried to create a tree:
let roots = e.filter((item) => !item?.parentid).map((e) => {e.children = [];return e;});
const children = e.filter((item) => item?.parentid);
function setChild(children) {
children.forEach((element) => {
const { parentid } = element;
const root = roots.find((e) => e.id === parentid);
if (root) root.children.push(element);
});
}
setChild(children);
I expect result when node has children elements:
let res = [
{
"id": 1,
"descr": "Те",
"moddate": "2023-02-09T13:37:46.366961",
"isgroup": true,
"children": [{
"id": 2,
"descr": "Те",
"moddate": "2023-02-09T13:37:46.366961",
"parentid": 1
}]
}];
So, my problem is that I dont make recursion, probably

Your code seems to work for the provided data. I think it does not work for a tree with height larger than 2, because you add a children array only to root nodes, but children can have children themselves. Similarly, you have to search for the parent in all nodes, not just roots. Otherwise, I think it works.
For fun, a one-liner as example:
let e = [
{
"id": 1,
"descr": "Те",
"moddate": "2023-02-09T13:37:46.366961",
"isgroup": true
}, {
"id": 2,
"descr": "Те",
"moddate": "2023-02-09T13:37:46.366961",
"parentid": 1
}, {
"id": 3,
"descr": "Те",
"moddate": "2023-02-09T13:37:46.366961",
"parentid": 1
}, {
"id": 4,
"descr": "Те",
"moddate": "2023-02-09T13:37:46.366961",
"parentid": 2
}];
const tree = e.map(n => ({...n, children: []})).reduce( (t,n, _, e) => 'parentid' in n ? (( n.parent = e.find(en => en.id === n.parentid)).children.push(n), t) : [...t, n], [])
console.log(tree)
.as-console-wrapper { max-height: 100% !important; top: 0; }

Sample implementation
Logic
Separate nodes with parentid and without parentid
Loop through nodesWithParent and find the matching parent from nodesWithOutParent.
If match found, push the march to the children node of the match.
If match not found, push the node to the list without parent id.
Retun the list
Code
let e = [
{
id: 1,
descr: "Те",
moddate: "2023-02-09T13:37:46.366961",
isgroup: true,
},
{
id: 2,
descr: "Те",
moddate: "2023-02-09T13:37:46.366961",
parentid: 1,
},
];
function setChild(list) {
const result = [];
const nodesWithParent = list.filter((item) => item?.parentid !== undefined);
const nodesWithOutParent = list.filter((item) => item?.parentid === undefined);
nodesWithParent.forEach(node => {
const match = nodesWithOutParent.find(item => item.id === node.parentid)
if (match) {
if (match.children) {
match.children.push(node)
} else {
match.children = [node];
}
} else {
nodesWithOutParent.push(match)
}
})
return nodesWithOutParent
}
console.log(setChild(e));

Related

Loop through an array of objects and update parent object count if child object exists

I am using Angular 13 and I have an array of objects like this:
[{
"name": "Operating System",
"checkedCount": 0,
"children": [{
"name": "Linux",
"value": "Redhat",
"checked": true
},
{
"name": "Windows",
"value": "Windows 10"
}
]
},
{
"name": "Software",
"checkedCount": 0,
"children": [{
"name": "Photoshop",
"value": "PS",
"checked": true
},
{
"name": "Dreamweaver",
"value": "DW"
},
{
"name": "Fireworks",
"value": "FW",
"checked": true
}
]
}
]
I would like to loop through the array, check if each object has a children array and it in turn has a checked property which is set to true, then I should update the checkedCount in the parent object. So, result should be like this:
[{
"name": "Operating System",
"checkedCount": 1,
"children": [{
"name": "Linux",
"value": "Redhat",
"checked": true
},
{
"name": "Windows",
"value": "Windows 10"
}
]
},
{
"name": "Software",
"checkedCount": 2,
"children": [{
"name": "Photoshop",
"value": "PS",
"checked": true
},
{
"name": "Dreamweaver",
"value": "DW"
},
{
"name": "Fireworks",
"value": "FW",
"checked": true
}
]
}
]
I tried to do it this way in angular, but this is in-efficient and results in an error saying this.allFilters[i].children[j] may be undefined. So, looking for an efficient manner to do this.
for(let j=0;i<this.allFilters[i].children.length; j++) {
if (Object.keys(this.allFilters[i].children[j]).length > 0) {
if (Object.prototype.hasOwnProperty.call(this.allFilters[i].children[j], 'checked')) {
if(this.allFilters[i].children[j].checked) {
this.allFilters[i].checkedCount++;
}
}
}
}
Use a nested for loop to check all the children. If checked is truthy, increment the count of the parent. You don't need to check if parent.children has any elements since if there are no elements the loop won't run anyways.
// minified data
const data = [{"name":"Operating System","checkedCount":0,"children":[{"name":"Linux","value":"Redhat","checked":!0},{"name":"Windows","value":"Windows 10"}]},{"name":"Software","checkedCount":0,"children":[{"name":"Photoshop","value":"PS","checked":!0},{"name":"Dreamweaver","value":"DW"},{"name":"Fireworks","value":"FW","checked":!0}]}];
for (const parent of data) {
for (const child of parent.children) {
if (child.checked) parent.checkedCount++;
}
}
console.log(data);
No need to complicate it like that, you just need to check checked property in children.
data.forEach((v) => {
v.children.forEach((child) => {
if (child.checked) {
v.checkedCount++;
}
});
});
Using filter + length on children array should do the job:
const data = [{"name":"Operating System","checkedCount":null,"children":[{"name":"Linux","value":"Redhat","checked":true},{"name":"Windows","value":"Windows 10"}]},{"name":"Software","checkedCount":null,"children":[{"name":"Photoshop","value":"PS","checked":true},{"name":"Dreamweaver","value":"DW"},{"name":"Fireworks","value":"FW","checked":true}]}];
data.forEach(itm => {
itm.checkedCount = itm.children?.filter(e => e.checked === true).length ?? 0;
});
console.log(input);
I would suggest going functional.
Using map
const children = arr.map(obj => obj.children);
const result = children.map((child, idx) => {
const checkedCount = child.filter(obj => obj.checked)?.length;
return {
...arr[idx],
checkedCount
};
});
console.log(result)
or using forEach
const result = [];
const children = arr.map(obj => obj.children);
children.forEach((child, idx) => {
const checkedCount = child.filter(obj => obj.checked)?.length;
result[idx] = {
...arr[idx],
checkedCount
};
});
console.log(result)

How to get distinct value from an array of objects containing array

I am trying to get an array of distinct values from the data structure below. I tried using reduce and object keys with no luck. What can I try next?
Data:
var data = [{
"id": 1,
"Technologies": ["SharePoint", "PowerApps"]
},
{
"id": 2,
"Technologies": ["SharePoint", "PowerApps", "SomethingElse"]
},
{
"id": 3,
"Technologies": ["SharePoint"]
},
{
"id": 4,
"Technologies": ["PowerApps"]
},
{
"id": 5,
"Technologies": null
}
]
Finished result should look like:
var distintValues = ["PowerApps", "SharePoint", "SomethingElse", null]
My attempt:
https://codepen.io/bkdigital/pen/MWEoLXv?editors=0012
You could use .flatMap() with a Set. .flatMap allows you to map each object's technology to one resulting array, and the Set allows you to remove the duplicates. With the help of optional chaining ?., you can also keep the null value (so it doesn't throw when accessing Technologies) like so:
const data = [{ "id": 1, "Technologies": ["SharePoint", "PowerApps"] }, { "id": 2, "Technologies": ["SharePoint", "PowerApps", "SomethingElse"] }, { "id": 3, "Technologies": ["SharePoint"] }, { "id": 4, "Technologies": ["PowerApps"] }, { "id": 5, "Technologies": null } ];
const res = [...new Set(data.flatMap(obj => obj?.Technologies))];
console.log(res);
[...new Set(
data
.map(v => Array.isArray(v.Technologies) ? v.Technologies : [v.Technologies])
.reduce((t, v) => [...t, ...v], [])
)];
I tried to solve this through JS. Here is my code:
const data = [{
"id": 1,
"Technologies": ["SharePoint", "PowerApps"]
}, {
"id": 2,
"Technologies": ["SharePoint", "PowerApps", "SomethingElse"]
}, {
"id": 3,
"Technologies": ["SharePoint"]
}, {
"id": 4,
"Technologies": ["PowerApps"]
}, {
"id": 5,
"Technologies": null
}]
const distintValues = [];
for (let element of data) {
if (element.Technologies != null) {
for (let elem of element.Technologies) {
if (!distintValues.includes(elem)) {
distintValues.push(elem);
}
}
}
}
console.log(distintValues);
In your attempt you tried to do it with reduce so here is how I would do it
var data = [{
"id": 1,
"Technologies": ["SharePoint", "PowerApps"]
},
{
"id": 2,
"Technologies": ["SharePoint", "PowerApps", "SomethingElse"]
},
{
"id": 3,
"Technologies": ["SharePoint"]
},
{
"id": 4,
"Technologies": ["PowerApps"]
},
{
"id": 5,
"Technologies": null
}
];
const objAsArray = Object.keys(data) // first we get the keys
.map(key => data[key]) // then we map them to their value
const technologyMap = objAsArray.reduce((acc, data) => {
// if the entry has technologies we set the key in the accumulation object to true
if (data.Technologies) {
data.Technologies.forEach(tech => acc[tech] = true)
}
return acc;
}, {})
// at the very end we get the keys of the accumulation object
const uniqueTechnologies =
Object.keys(
technologyMap
)

Flat JSON unflatten to hierarchy with multiple parents as String

I am trying to unflatten some json-data. If i use my test data like following everything works fine!
var data = [
{ "title": 1, "parentids": [0] },
{ "title": 2, "parentids": [1] },
{ "title": 3, "parentids": [1] },
{ "title": 4, "parentids": [2, 3] },
];
So if i use my function for this dataset i receive the following structure and that is actually what I want.
[
{
"title": 0,
"parentids": [],
"children": [
{
"title": 1,
"parentids": [
0
],
"children": [
{
"title": 2,
"parentids": [
1
],
"children": [
{
"title": 4,
"parentids": [
2,
3
],
"children": []
}
]
},
{
"title": 3,
"parentids": [
1
],
"children": [
{
"title": 4,
"parentids": [
2,
3
],
"children": []
}
]
}
]
}
]
}
]
BUT! My data has changed.And unfortunately my title and my parentids are now string values
var data = [
{ "title": "any", "parentids": [""] },
{ "title": "culture", "parentids": ["any"] },
{ "title": "building", "parentids": ["any"] },
{ "title": "museum", "parentids": ["culture", "building"] },
];
I really tried a lot to change and edit my exisiting code, but it wont work...either there is no output or the hierarchy is not like expected.Here is my actual function, which works for the first dataset. How could i change it, that it will work for string parentids;
function unflatten(arr) {
var node,
graph = [],
mapped = [];
// First map the nodes of the array to an object
for (var i = 0, len = arr.length; i < len; i++) {
node = arr[i];
mapped[node.title] = node;
mapped[node.title]['children'] = [];
}
// 2. assign children:
mapped.forEach(function (node) {
// Add as child to each of the parents
node.parentids.forEach(function (parentid) {
if (mapped[parentid]) {
mapped[parentid]['children'].push(node);
} else {
// If parent does not exist as node, create it at the root level,
// and add it to first level elements array.
graph.push(mapped[parentid] = {
title: parentid, //name in this case its 0
parentids: [],
children: [node]
});
}
});
});
return graph;
};
var graph = unflatten(types);
console.log(JSON.stringify(graph, null, 4));
document.body.innerHTML = "<pre>" + (JSON.stringify(graph, null, " "))
Im not sure but i think the 2nd part with "if (mapped[parentid]" causes the issue? Because I am using now strings instead of integers? I really dont know how to continue... I appreciate any kind of hint or solution!
Thanks in advance and have a nice day/week
You could use this solution:
var data = [
{ "title": "any", "parentids": [] },
{ "title": "culture", "parentids": ["any"] },
{ "title": "building", "parentids": ["any"] },
{ "title": "museum", "parentids": ["culture", "building"] },
]
// For each object in data, assign a children property.
data.forEach(o => o.children = [])
// For each object in data, assign a key/object pair using the title e.g
// {
// culture: { "title": "culture", "parentids": ["any"] }}
// ...
// }
const map = data.reduce((a, o) => (a[o.title] = o, a), {})
// For each object in data, and for each parentid in that object,
// push this object to the object where the given parentid === ID
data.forEach(o => o.parentids.forEach(id => map[id] && map[id].children.push(o)))
// Filter the data object to only root elements (where there are no parentids)
const output = data.filter(e => !e.parentids.length)
console.log(output);
This is the code i ended up with
var types1 = [
{ "title": "any", "parentids": [] },
{ "title": "culture", "parentids": ["any"] },
{ "title": "building", "parentids": ["any"] },
{ "title": "museum", "parentids": ["culture", "building"] },
];
function unflatten(arr) {
var node,
graph = [],
mapped = {};
// First map the nodes of the array to an object -> create a hash table.
for (var i = 0, len = arr.length; i < len; i++) {
node = arr[i];
mapped[node.title] = node;
mapped[node.title]['children'] = [];
}
// 2. assign children:
for (var index in mapped) {
if (mapped[index].parentids.length) {
mapped[index].parentids.forEach(function (parentid) {
mapped[parentid]['children'].push(mapped[index]);
});
} else {
graph.push(mapped[index] = {
title: mapped[index].parentids,
parentids: [],
children: [mapped[index]]
});
}
};
return graph;
};
var graph = unflatten(types1);
console.log(JSON.stringify(graph, null, 4));
document.body.innerHTML = "<pre>" + (JSON.stringify(graph, null, " "))

trim JS object to remove extra params with curly braces

I have an object response here
result.joblist = {
"collection_job_status_list": [
{
"application_context": {
"application_id": "a4",
"context_id": "c4"
},
"creation_time": "15699018476102",
"progress": 100,
"status": "READY",
"phase": "ACTIVE",
"job_error": {}
},
{
"application_context": {
"application_id": "a6",
"context_id": "c6"
},
"creation_time": "15698648632523",
"progress": 100,
"status": "READY",
"phase": "ACTIVE",
"job_error": {}
}
],
"result": {
"request_result": "ACCEPTED",
"error": {}
}
}
Need to get rid of {"application_context": & ending } here, just need application_id":"a4","context_id":"c4" at the same level.
I have tried something like this, but not able to move ahead.
var newObj: any = {};
if (allJobs && allJobs.length > 0) {
// this.rowData = this.allJobs;
// this.allJobs = this.allJobs['application_id'];
//let ele:object = allJobs.application_context;
allJobs.forEach(ele => {
newObj = {
application_id: ele.application_context.application_id,
context_id: ele.application_context.application_context
};
return newObj;
});
}
You can use map and destructuring
Get the collection_job_status_list from result
Loop over the values take out the required values from application_context key and merge with remaining values
Build the same structure as original result
let result = {"collection_job_status_list": [{"application_context": {"application_id": "a4","context_id": "c4"},"creation_time": "15699018476102","progress": 100,"status": "READY","phase": "ACTIVE","job_error": {}},{"application_context": {"application_id": "a6","context_id": "c6"},"creation_time": "15698648632523","progress": 100,"status": "READY","phase": "ACTIVE","job_error": {}}],"result": {"request_result": "ACCEPTED","error": {}}}
let { collection_job_status_list, ...rest } = result
let modified = collection_job_status_list.map(({
application_context: {
application_id,
context_id
},
...rest
}) => ({ ...rest, context_id, application_id}))
let final = {
collection_job_status_list: modified,
...rest
}
console.log(final)
What you need here is a map. Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Try modifying the code as below and you should have your expected output.
var newObj: any = {};
var newArray;
if (allJobs && allJobs.length > 0) {
newArray = allJobs.map(ele => {
newObj = {
application_id: ele.application_context.application_id,
context_id: ele.application_context.application_context
};
return newObj;
});
}
Hope this helps :)
You need to use the map function
const result = {};
result.joblist = {
"collection_job_status_list": [
{
"application_context": {
"application_id": "a4",
"context_id": "c4"
},
"creation_time": "15699018476102",
"progress": 100,
"status": "READY",
"phase": "ACTIVE",
"job_error": {}
},
{
"application_context": {
"application_id": "a6",
"context_id": "c6"
},
"creation_time": "15698648632523",
"progress": 100,
"status": "READY",
"phase": "ACTIVE",
"job_error": {}
}
],
"result": {
"request_result": "ACCEPTED",
"error": {}
}
}
result.joblist.collection_job_status_list = result.joblist.collection_job_status_list.map(item => {
return {
"application_id": item.application_context.application_id,
"context_id": item.application_context.context_id
}
})
console.log(result)
Or you can use map function along with shorthand es6 syntaxes
const result = {};
result.joblist = {
"collection_job_status_list": [
{
"application_context": {
"application_id": "a4",
"context_id": "c4"
},
"creation_time": "15699018476102",
"progress": 100,
"status": "READY",
"phase": "ACTIVE",
"job_error": {}
},
{
"application_context": {
"application_id": "a6",
"context_id": "c6"
},
"creation_time": "15698648632523",
"progress": 100,
"status": "READY",
"phase": "ACTIVE",
"job_error": {}
}
],
"result": {
"request_result": "ACCEPTED",
"error": {}
}
}
result.joblist.collection_job_status_list = result.joblist.collection_job_status_list.map(({application_context}) => {
return {
...application_context
}
})
console.log(result)
EDIT:
The things you want to keep in your array depends on what you are returning from the map. You return the keys you want to keep. So if you want other items.
If you have an array A
A = [
{
nest: {
x: 1,
y: 1,
},
key1: 5,
key2: 7,
},
{
nest: {
x: 1,
y: 1,
},
key1: 5,
key2: 7,
},
{
nest: {
x: 1,
y: 1,
},
key1: 5,
key2: 7,
}
]
Let us assume you want x from nest, key1 and key2 in your final output. You will then do
const finalOutput = A.map(item => {
return {
x: item.nest.x, // x from nest
key1: item.key1, // key1
key2: item.key2, // key2
}
})
But there are shorthand forms for doing this. Let's assume that the item we get in our map function is already divided into a nest and a rest variable. The nest contains item.nest and the rest is {key1: 5,key2:7}, then you can simply return x from nest and everything else from rest
const finalOutput = A.map(({nest, ...rest}) => {
return {
x: nest.x, // x from nest
...rest, // everything else
}
})

Sort-Index from nested JSON with Javascript

How can I recursively add a sort key to an infinite hierarchy like this:
[
{
"id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"name": "A",
"parent_id": null,
"sortNr": 1,
"children": [
{
"id": "07E556EE-F66F-49B5-B5E4-54AFC6A4DD9F",
"name": "A-C",
"parent_id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"sortNr": 3,
"children": []
},
{
"id": "8C63981E-0D30-4244-94BE-658BAAF40EF3",
"name": "A-A",
"parent_id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"sortNr": 1,
"children": [
{
"id": "0BA32F23-A2CD-4488-8868-40AD5E0D3F09",
"name": "A-A-A",
"parent_id": "8C63981E-0D30-4244-94BE-658BAAF40EF3",
"sortNr": 1,
"children": []
}
]
},
{
"id": "17A07D6E-462F-4983-B308-7D0F6ADC5328",
"name": "A-B",
"parent_id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"sortNr": 2,
"children": []
}
]
},
{
"id": "64535599-13F1-474C-98D0-67337562A621",
"name": "B",
"parent_id": null,
"sortNr": 2,
"children": []
},
{
"id": "1CE38295-B933-4457-BBAB-F1B4A4AFC828",
"name": "C",
"parent_id": null,
"sortNr": 3,
"children": [
{
"id": "D1E02274-33AA-476E-BA31-A4E60438C23F",
"name": "C-A",
"parent_id": "1CE38295-B933-4457-BBAB-F1B4A4AFC828",
"sortNr": 1,
"children": [
{
"id": "76A8259C-650D-482B-91CE-D69D379EB759",
"name": "C-A-A",
"parent_id": "D1E02274-33AA-476E-BA31-A4E60438C23F",
"sortNr": 1,
"children": []
}
]
}
]
}
]
I want to get a sortable index.
For example 0000.0001.0003 or 0001.0003 for node A-C.
The function for leadingZeroes is
function fillZeroes (num) {
var result = ('0000'+num).slice(-4);
if (num===null){
return result
} else {
return '0000';
}
}
It should be sorted by sort number in each level of hierarchy, the sort number should be set newly every time, because I want to do rearrangement by setting it 1,5 to insert it between 1 and 2 (later for drag and drop capability). so 1;1,5;2 should become 1;2;3 and can then be translated to a sort-index like above.
I will also need it for indentation and breadcrumb-stuff.
How do I insert the proper sort-index to each object ?
The question is mainly about the recursion part. I am quite new to JavaScript
Thanks a lot
Based on great answer by #georg. A bit adjusted solution based on sortNr object property.
You can run it straight as is with json being your object. The sort index is written into sortOrder property.
// Mutates the given object in-place.
// Assigns sortOrder property to each nested object
const indexJson = (json) => {
const obj = {children: json};
const format = (xs) => xs.map(x => pad(x, 4)).join('.');
const pad = (x, w) => (10 ** w + x).toString().slice(-w);
const renumber = (obj, path) => {
obj.path = path;
obj.sortOrder = format(path);
obj.children.slice()
.sort((obj1, obj2) => obj1.sortNr - obj2.sortNr)
.forEach((c, n) => renumber(c, path.concat(n+1)));
};
renumber(obj, []);
};
indexJson(json);
console.log(JSON.stringify(json, null, 2));
Basically
let renumber = (obj, path) => {
obj.path = path
obj.children.forEach((c, n) => renumber(c, path.concat(n)))
}
renumber({children: yourData}, [])
this creates a path property, which is an array of relative numbers. If you want to format it in a special way, then you can do
obj.path = format(path)
where format is like
let format = xs => xs.map(pad(4)).join(',')
let pad = w => x => (10 ** w + x).toString().slice(-w)

Categories

Resources