How to flatten an object that has array of objects in Javascript - javascript

I am trying to solve this question it needs me to flatten this object parent that it has children each parent has 2 children, and each child has 2 children and so on....
My goal is to flatten this to one single object.
const par = {
id: 1,
name: "parent",
children: [{
id: 2,
name: "child 1",
children:[{
id: 4,
name: "child 3",
children: [],
},{
id: 5,
name: "child 4 ",
}]
},{
id: 3,
name: "child 2",
children: [{
id: 6,
name: "child 5",
},{
id: 7,
name: "child 6",
children: []
}]
}]
}
I tried function, but it returns an array from
Deep Flatten JavaScript Object Recursively
function flat(r, a) {
let b = {};
Object.keys(a).forEach(function (k) {
if (k !== 'children') {
b[k] = a[k];
}
});
r.push(b);
if (Array.isArray(a.children)) {
b.children = a.children.map(function (a) { return a.id;});
return a.children.reduce(flat, r);
}
return r;
}

You still owe us a description of your desired output. But if you want something as simple as this:
[
{id: 1, name: "parent"},
{id: 2, name: "child 1"},
{id: 4, name: "child 3"},
{id: 5, name: "child 4"},
{id: 3, name: "child 2"},
{id: 6, name: "child 5"},
{id: 7, name: "child 6"}
]
Then a depth-first recursive function can be as simple as this:
const flatten = ({children = [], ...rest}) => [rest, ...children .flatMap (flatten)]
const par = {id: 1, name: "parent", children: [{id: 2, name: "child 1", children: [{id: 4, name: "child 3", children: []}, {id: 5, name: "child 4 ", }]}, {id: 3, name: "child 2", children: [{id: 6, name: "child 5", }, {id: 7, name: "child 6", children: []}]}]}
console .log (flatten (par))
.as-console-wrapper {max-height: 100% !important; top: 0}
If you wanted to include a parentId field, using null for root-level objects, it's only slightly more complex:
const flatten = ({id, children = [], ...rest}, parentId = null) => [
{id, ...rest, parentId}, ...children .flatMap (c => flatten(c, id))
]

Here's an effective technique using a recursive generator flat -
function *flat({ children = [], ...t }, parentId = null) {
yield { ...t, parentId }
for (const child of children)
yield *flat(child, t.id)
}
const par = {id: 1,name: "parent",children: [{id: 2,name: "child 1",children:[{id: 4,name: "child 3",children: [],},{id: 5,name: "child 4 ",}]},{id: 3,name: "child 2",children: [{id: 6,name: "child 5",},{id: 7,name: "child 6",children: []}]}]}
console.log(Array.from(flat(par)))
.as-console-wrapper { min-height: 100%; top: 0; }
You can collect all the results of a generator using Array.from -
[
{
"id": 1,
"name": "parent",
"parentId": null
},
{
"id": 2,
"name": "child 1",
"parentId": 1
},
{
"id": 4,
"name": "child 3",
"parentId": 2
},
{
"id": 5,
"name": "child 4 ",
"parentId": 2
},
{
"id": 3,
"name": "child 2",
"parentId": 1
},
{
"id": 6,
"name": "child 5",
"parentId": 3
},
{
"id": 7,
"name": "child 6",
"parentId": 3
}
]
Or you can simply iterate thru the generator's result directly -
for (const flatNode of flat(par)) {
// do something with flatNode ...
}
See this related Q&A for a technique to convert the flat tree back to a recursive tree or graph.

You can try this
function flatTree(tree, parentId = null) {
const { id, name, children } = tree;
const result = [{ id, name, parentId }];
if (Array.isArray(children)) {
children.forEach((child) => {
result.push(...flatTree(child, id));
});
}
return result;
}
const par = {
id: 1,
name: "parent",
children: [
{
id: 2,
name: "child 1",
children: [
{
id: 4,
name: "child 3",
children: [],
},
{
id: 5,
name: "child 4 ",
},
],
},
{
id: 3,
name: "child 2",
children: [
{
id: 6,
name: "child 5",
},
{
id: 7,
name: "child 6",
children: [],
},
],
},
],
};
console.log(flatTree(par));
/**
* Output:
* [
{ id: 1, name: 'parent', parentId: null },
{ id: 2, name: 'child 1', parentId: 1 },
{ id: 4, name: 'child 3', parentId: 2 },
{ id: 5, name: 'child 4 ', parentId: 2 },
{ id: 3, name: 'child 2', parentId: 1 },
{ id: 6, name: 'child 5', parentId: 3 },
{ id: 7, name: 'child 6', parentId: 3 }
]
*/

Here is a solution using object-scan. Reinventing the wheel is typically not as bug-free, flexible or maintainable as using a battle-tested library!
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.4.0/lib/index.min.js';
const par = { id: 1, name: 'parent', children: [{ id: 2, name: 'child 1', children: [{ id: 4, name: 'child 3', children: [] }, { id: 5, name: 'child 4 ' }] }, { id: 3, name: 'child 2', children: [{ id: 6, name: 'child 5' }, { id: 7, name: 'child 6', children: [] }] }] };
const fn = objectScan(['**{children[*]}.id'], {
rtn: ({ parent: { id, name } }) => ({ id, name })
});
const r = fn(par);
console.log(r);
/* => [
{ id: 7, name: 'child 6' },
{ id: 6, name: 'child 5' },
{ id: 3, name: 'child 2' },
{ id: 5, name: 'child 4 ' },
{ id: 4, name: 'child 3' },
{ id: 2, name: 'child 1' },
{ id: 1, name: 'parent' }
] */
</script>
Disclaimer: I'm the author of object-scan

Related

Create an Array Tree of Objects with different levels and positions

I want to generate a Array Tree of Objects with different levels and positions. In my opinion the parentId can create the level as in "children". The position can sort the items.
Unlimited in levels and positions.
Can somebody help me out how I can achieve this?
I receive the following API data:
[
{ label: "Level one 1", id: 1, parentId: null, position: 0},
{ label: "Level two 1-1", id: 4, parentId: 1, position: 0},
{ label: "Level three 1-1-1", id: 9, parentId: 4, position: 1},
]
Here is an example how I want the data in the end:
const dataSource = ref([
{
id: 1,
position: 0,
parentId: null,
label: 'Level one 1',
children: [
{
id: 4,
position: 0,
parentId: 1,
label: 'Level two 1-1',
children: [
{
id: 9,
parentId: 4,
position: 0,
label: 'Level three 1-1-1',
},
{
id: 10,
parentId: 4,
position: 1,
label: 'Level three 1-1-2',
},
],
},
],
},
{
id: 2,
position: 1,
parentId: null,
label: 'Level one 2',
children: [
{
id: 5,
position: 0,
parentId: 2,
label: 'Level two 2-1',
},
{
id: 6,
position: 1,
parentId: 2,
label: 'Level two 2-2',
},
],
},
{
id: 3,
position: 2,
parentId: null,
label: 'Level one 3',
children: [
{
id: 7,
position: 0,
parentId: 3,
label: 'Level two 3-1',
},
{
id: 8,
position: 1,
parentId: 3,
label: 'Level two 3-2',
},
],
},
])
for this you can combine map method to redefine item in your array and filter to get only child of a parent object
var data = [
{ label: "Level one 1", id: 1, parentId: null, position: 0},
{ label: "Level two 1-1", id: 4, parentId: 1, position: 0},
{ label: "Level three 1-1-1", id: 9, parentId: 4, position: 1},
];
var result = data.map(elem => {
elem.children = data.filter(item => item.parentId === elem.id);
return elem;
});
console.log(result);
You could take a ingle loop approach with an object as reference for each node.
const
getTree = (data, root) => {
const t = {};
data.forEach(o => ((t[o.parentId] ??= {}).children ??= []).push(Object.assign(t[o.id] ??= {}, o)));
return t[root].children;
},
data = [{ label: "Level one 1", id: 1, parentId: null, position: 0 }, { label: "Level two 1-1", id: 4, parentId: 1, position: 0 }, { label: "Level three 1-1-1", id: 9, parentId: 4, position: 1 }],
tree = getTree(data, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This solution first create a copy of each object, and stores it in a Map instance. I manually add the key null with value { children: [] }, so we can handle/find the root object easily.
After creating the Map instance, I loop over each records. Fetch both the copy of the record itself and the parent. Then assign parent.children to an array if it's not present. Finally I assign the record as a child of parent based on its position.
function recordsToTree(records) {
const lookup = new Map(records.map(({ ...record }) => [record.id, record]));
lookup.set(null, { children: [] });
for (const { id, parentId, position } of records) {
const record = lookup.get(id);
const parent = lookup.get(parentId);
parent.children ||= [];
parent.children[position] = record;
}
return lookup.get(null).children;
}
console.log(
recordsToTree([
{ label: "Level three 1-1-2", id: 3, parentId: 4, position: 1 },
{ label: "Level one 1", id: 1, parentId: null, position: 0 },
{ label: "Level three 1-1-1", id: 9, parentId: 4, position: 0 },
{ label: "Level two 1-1", id: 4, parentId: 1, position: 0 },
])
);
If position does not reflect the index of a child element you could use parent.children.push(record) instead of parent.children[position] = record.

Returning ids & its key from an object within an array

I have a multidimensional javascript array of objects that I am trying to use to simply collate both the id and its key within the unit array to a brand new array
What is the best solution for returning the id with the key within its units array but reversed so the key of the new array is the unit id
[
{
units: [
{
id: 10000282,
name: "Group 1",
},
{
id: 10000340,
name: "Group 2",
},
{
id: 10000341,
name: "Group 3",
},
],
},
{
units: [
{
id: 10000334,
name: "Group 4",
},
],
},
]
Expected output - just return an array in the following format
e.g
ids = [ 10000282 => 0, 10000340 => 1, 10000341 => 2, 10000334 => 0 ]
so 10000282 would be the key, and 0 would be the value for the first iteration of the array
-- update --
I probably didn't explain the output so well the output should be as follows but in an array format.
ids[10000282] = 0
ids[10000340] = 1
ids[10000341] = 2
ids[10000334] = 0
So, I suppose you want to get the results back into a dictionary with key the nested id and value its index in the wrapping units. You can easily do that as follows:
x = [
{
units: [
{
id: 10000282,
name: "Group 1",
},
{
id: 10000340,
name: "Group 2",
},
{
id: 10000341,
name: "Group 3",
},
],
},
{
units: [
{
id: 10000334,
name: "Group 4",
},
],
},
];
result = x.flatMap(el => el.units.map((e,i) => ({[e.id]: i})));
console.log(result);
Here's a slightly different approach using reduce:
const data = [{
units: [{
id: 10000282,
name: "Group 1",
},
{
id: 10000340,
name: "Group 2",
},
{
id: 10000341,
name: "Group 3",
},
],
},
{
units: [{
id: 10000334,
name: "Group 4",
}, ],
},
];
const result = data.reduce(
(total, current) =>
total.concat(current.units.map(({ id }, i) => ({ [id]: i }))),
[]
);
console.log(result);
It sounds like you want to the able to access the id properties directly which points to refactoring into an object or Map.
Using an Object created using Object.fromEntries()
const arr = [{ units: [{ id: 10000282, name: "Group 1", }, { id: 10000340, name: "Group 2", }, { id: 10000341, name: "Group 3", },], }, { units: [{ id: 10000334, name: "Group 4", },], },];
const result = Object.fromEntries(arr.flatMap(({ units }) => units.map(({ id }, i) => [id, i])));
console.log(result);
// { '10000282': 0, '10000334': 0, '10000340': 1, '10000341': 2 }
console.log('result[10000340] = ', result[10000340])
// result[10000340] = 1
Using a Map
const arr = [{ units: [{ id: 10000282, name: "Group 1", }, { id: 10000340, name: "Group 2", }, { id: 10000341, name: "Group 3", },], }, { units: [{ id: 10000334, name: "Group 4", },], },];
const result = new Map(arr.flatMap(({ units }) => units.map(({ id }, i) => [id, i])));
// Map(4) { 10000282 => 0, 10000340 => 1, 10000341 => 2, 10000334 => 0 }
console.log('result.get(10000340) = ', result.get(10000340))
// result.get(10000340) = 1
const arrays = [
{
units: [
{
id: 10000282,
name: "Group 1",
},
{
id: 10000340,
name: "Group 2",
},
{
id: 10000341,
name: "Group 3",
},
],
},
{
units: [
{
id: 10000334,
name: "Group 4",
},
],
},
];
const results = arrays.flatMap((items) => items.units.map(({id}, index) => `ids[${id}] = ${index}`));
console.log(...results);

How do I output the child array correctly? [duplicate]

This question already has answers here:
Build tree array from flat array in javascript
(34 answers)
Closed 1 year ago.
How do I output the child array correctly?
const arr = [
[{ id: 0, parrentId: null, title: "Main tab", parrent: true }],
[{ id: 1, parrentId: 0, title: "Main child 1", parrent: false }],
[{ id: 2, parrentId: 0, title: "Main child 2", parrent: false }],
[{ id: 3, parrentId: 2, title: "Main tab 2", parrent: true }],
[{ id: 4, parrentId: 3, title: "Main child tab 2", parrent: false }]
];
How to output arrays according to child > parrent? But so that the id is parrentId === id
Main tab:
1) Main child 1
2) Main child 2
1) Main tab 2
1)Main child tab 2
I'm trying to
if (arr) {
const child = arr.filter(({ hasChild }) => hasChild);
const notChild = arr.filter(({ hasChild }) => !hasChild);
const test = { ...child, notChild };
console.log(test);
}
If I understood correctly, maybe it is something like this:
const arr = [
[{ id: 0, parentId: null, title: "Main tab", parent: true }],
[{ id: 1, parentId: 0, title: "Main child 1", parent: false }],
[{ id: 2, parentId: 0, title: "Main child 2", parent: false }],
[{ id: 3, parentId: 2, title: "Main tab 2", parent: true }],
[{ id: 4, parentId: 3, title: "Main child tab 2", parent: false }]
];
arr.reduce((acc, data) => {
if(data[0].parent) {
acc = acc.concat(data[0]);
} else {
acc = acc.map(ac => {
if(ac.id === data[0].parentId) {
return {
...ac,
child: ac.child ? ac.child.concat(data[0]) : [].concat(data[0])
};
} else {
return ac;
}
});
}
return acc;
}, []);

Push data to parent in a recursive function

I have the following object:
let model = {
id: 1,
name: "model 1",
children: [{
id: 2,
name: "sub model 1",
children: [{
id: 3,
name: "criteria 1",
isCriteria: true,
answer: {
mark: 4
}
},
{
id: 4,
name: "criteria 2",
isCriteria: true
}
]
},
{
id: 5,
name: "sub model 2",
children: [{
id: 6,
name: "criteria 3",
isCriteria: true,
answer: {
mark: 4
}
},
{
id: 7,
name: "criteria 4",
isCriteria: true,
answer: {
mark: 2
}
}
]
}
]
};
I want in result the following object:
{
name: "model 1",
answer: {
mark: 3.5,
completion: 75
},
children: [{
name: "sub model 1",
answer: {
mark: 4,
completion: 50
},
children: [{
name: "criteria 1",
isCriteria: true,
answer: {
mark: 4
}
},
{
name: "criteria 2",
isCriteria: true
}
]
},
{
name: "sub model 2",
answer: {
mark: 3,
completion: 100
},
children: [{
name: "criteria 3",
isCriteria: true,
answer: {
mark: 4
}
},
{
name: "criteria 4",
isCriteria: true,
answer: {
mark: 2
}
}
]
}
]
}
EXPLICATION:
I want to push to every parent the following answer object:
{
mark: the sum of marks of all children/total of children (exclude children with no answer),
completion: (the sum of children with answer/total children) * 100
}
!! NB: The depth of the object is unknown.
I tried the following function, But it adds the answer object only to the first parent before last depth
function loopThoughModelChildren(node, parent) {
if (node == null) {
return;
};
if (node.isCriteria) {
if (!parent.tempAnswer) {
parent.tempAnswer = [];
}
parent.tempAnswer.push({ child: node.id, answer: node.answer });
}
if (node.children)
node.children.forEach(child => loopThoughModelChildren(child, node));
}
You could take a recursive approach with a look to the children property.
Then generate new nodes and assign the answers.
function convert(node) {
if (!node.children) return node;
var marks = 0,
completions = 0,
count = 0,
children = node.children.map(node => {
node = convert(node);
if (node.answer) {
marks += node.answer.mark;
completions += node.answer.completion || 100;
count++;
}
return node;
});
return { ...node, answer: { mark: marks / count, completion: completions / node.children.length }, children };
}
var model = { id: 1, name: "model 1", children: [{ id: 2, name: "sub model 1", children: [{ id: 3, name: "criteria 1", isCriteria: true, answer: { mark: 4 } }, { id: 4, name: "criteria 2", isCriteria: true }] }, { id: 5, name: "sub model 2", children: [{ id: 6, name: "criteria 3", isCriteria: true, answer: { mark: 4 } }, { id: 7, name: "criteria 4", isCriteria: true, answer: { mark: 2 } }] }] },
result = convert(model);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Treeview in javascript

I need to make something similar to treeview. It doesn't need collapsing it just needs to show some heirachy, but in a table view.
Flat data comes in from a database. I unflattened it and made a tree, but now that it's a tree, I wanted to turn it back into an array, so I can easily iterate using a for loop.
After looking at the source code of other treeviews my method was going to be like this:
From flat data from a db, unflatten:
[
{ id: 1, name: 'node1', parentId: 0 },
{ id: 2, name: 'node2', parentId: 1 },
{ id: 4, name: 'node4', parentId: 2 },
{ id: 5, name: 'node5', parentId: 2 },
{ id: 6, name: 'node6', parentId: 3 },
{ id: 3, name: 'node3', parentId: 1 },
]
The tree is now ordered and has a hierarchy (levels for indentation). Traverse the tree. I add level and children.
[
id: 1,
name: 'node1',
level: 0,
children: [
{
id: 2,
name: 'node2',
parentId: 1,
level: 1
children: [
{
id: 4,
name: 'node4',
parentId: 2,
level: 2,
children: []
},
{
id: 5,
name: 'node5',
parentId: 2,
children: []
},
]
},
{
id: 3,
name: 'node1',
parentId: 1,
children: [
{
id: 6,
name: 'node6',
parentId: 3,
children: []
},
]
},
]
]
Compress it back into an array form with order, level.
[
{ id: 1, name: 'node1', level: 0, parentId: 0, children: [...] },
{ id: 2, name: 'node2', level: 1, parentId: 1, children: [...] },
{ id: 4, name: 'node4', level: 2, parentId: 2, children: [...] },
{ id: 5, name: 'node5', level: 2, parentId: 2, children: [...] },
{ id: 3, name: 'node3', level: 1, parentId: 1, children: [...] },
{ id: 6, name: 'node6', level: 2, parentId: 3, children: [...] },
]
Of which I can easily create a table from.
I've gotten close with the following code:
var data = [
{ id: 1, name: 'node1', parentId: 0 },
{ id: 2, name: 'node2', parentId: 1 },
{ id: 4, name: 'node4', parentId: 2 },
{ id: 5, name: 'node5', parentId: 2 },
{ id: 6, name: 'node6', parentId: 3 },
{ id: 3, name: 'node3', parentId: 1 }
]
function unflatten (arr, parentId, level) {
let output = []
for (const obj of arr) {
if (obj.parentId === parentId) {
var children = unflatten(arr, obj.id, level+1)
obj.level = level
if (children.length) {
obj.children = children
}
output.push(obj)
}
}
// console.log(output)
return output
}
function flatten (tree) {
var output = []
for(const node of tree) {
if(node.children !== undefined){
var nodeChildren = flatten(node.children.reverse())
for(const child of nodeChildren){
output.push(child)
}
}
output.push(node)
}
return output
}
var dataCopy = Object.assign([], data)
console.log('data', dataCopy)
var res = unflatten(data, 0, 0)
console.log('tree', res)
var resCopy = Object.assign([], res)
var res2 = flatten(resCopy)
console.log('reflatten', res2)
Fiddle http://jsfiddle.net/ctd09r85/10/
That fiddle is the closest I've gotten, but it's a bit reversed and out of order.
How can I do this, and is this a reasonable way to build the tree view.

Categories