I want to keep the parent-child relationship of the tree node.
I have a JSON tree, like this
{
id: null,
children: [
{
id: 1,
children: [
{
id: 11,
children: [
{
id: 111
children: []
}
]
},
{
id: '12',
children: []
},
]
},
{
id: '2',
children: [
{
id: '21',
children: []
},
{
id: '22',
children: [
{
id: '221',
children: []
}
]
},
]
},
]
}
I want flat the tree, like this
[
{ id: 1, parent: null,},
{ id: 11, parent: 1, },
{ id: 111, parent: 11, },
{ id: 2, parent: null, },
{ id: 21, parent: 2, },
...
]
parent automatic generated
Is there any good way?
You could use flatMap method and create recursive function that will return 1D array as a result.
const data = {"id":null,"children":[{"id":1,"children":[{"id":11,"children":[{"id":111,"children":[]}]},{"id":"12","children":[]}]},{"id":"2","children":[{"id":"21","children":[]},{"id":"22","children":[{"id":"221","children":[]}]}]}]}
const flatten = (data, parent = null) =>
data.flatMap(({ id, children }) => ([
{ id, parent },
...flatten(children, id)
]))
const result = flatten(data.children);
console.log(result)
You could get the object and return an array of the flat children.
const
getFlat = ({ id, children = [] }) =>
children.flatMap(o => [{ id: o.id, parent: id }, ...getFlat(o)]);
var data = { id: null, children: [{ id: 1, children: [{ id: 11, children: [{ id: 111, children: [] }] }, { id: '12', children: [] }] }, { id: '2', children: [{ id: '21', children: [] }, { id: '22', children: [{ id: '221', children: [] }] }] }] },
flat = getFlat(data);
console.log(flat);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If compatbility with Internet Explorer is important, then the following approach which avoids the need for Array#flatMap might suit:
const input={id:null,children:[{id:'1',children:[{id:'11',children:[{id:'111',children:[]}]},{id:'12',children:[]}]},{id:'2',children:[{id:'21',children:[]},{id:'22',children:[{id:'221',children:[]}]}]}]};
const result = [];
// Define recursive function to walk through the data tree
// and produce a flat array of required data
const recurse = (item, parent) => {
// Only add item to result if it has valid id
if (item.id) {
result.push({
id: item.id,
parent: parent ? parent.id : null
});
}
// Iterate the children of this item, traversing and
// processing them in the same way
item.children.forEach(child => recurse(child, item))
}
recurse(input);
console.log(result);
You can use a simple recursion to achieve this
let data = {
id: 1,
name: 'a1',
children: [{
id: 2,
name: 'a2',
children: [{
id: 3,
name: 'b1',
children: []
},
{
id: 4,
name: 'b2',
children: []
}
]
}]
}
function flatten(data){
output = [];
return (function indentHandler(data, level, parent){
output.push({id: data['id'], parent: parent})
if (data['children'].length === 0){
return output;
}
level += 1;
data['children'].forEach(function(child){
return indentHandler(child, level, data.name);
});
return output;
})(data, 0, null);
}
flatten(data);
use recursion
var tree = {
id: null,
children: [{
id: 1,
children: [{
id: 11,
children: [{
id: 111,
children: []
}]
},
{
id: '12',
children: []
},
]
},
{
id: '2',
children: [{
id: '21',
children: []
},
{
id: '22',
children: [{
id: '221',
children: []
}]
},
]
}
]
};
var flat = [];
function flatArray(arr, parentId) {
arr.forEach(function(el) {
flat.push({
id: el.id,
parent: parentId || null
});
if (el.children)
flatArray(el.children, el.id);
});
}
flatArray(tree.children, 0)
console.log(flat);
Related
I'm trying to find an element on a multidimentionnal array usin JAVASCRIPT function, but I get error
This is my array's data:
export const datas = [
{
id: 1,
firstName: 'John',
tables: [
{ ID: 11, title: 'Lorem' },
{ ID: 12, title: 'Ipsum' },
],
},
{
id: 2,
firstName: 'Doe',
tables: [
{
ID: 22,
title: 'Arke',
nodes: [{ name: 'Name1' }, { name: 'Name2' }, { name: 'Name3' }],
},
{ ID: 23, title: 'Korem' },
],
},
{
id: 3,
firstName: 'Brad',
tables: [
{
ID: 30,
title: 'Mern',
nodes: [{ name: 'Name4' }, { name: 'Name5' }, { name: 'Name6' }],
},
{
ID: 31,
title: 'Full',
nodes: [{ name: 'Name7' }, { name: 'Name8' }, { name: 'Name9' }],
},
],
},
];
I've tried a reccursive function but it's not work, this is my code :
export const findById = (arr, id) => {
for (let o of arr) {
if (o.tables.length > 0) {
let a = findById(o.tables.nodes, 'id');
console.log(a);
}
}
};
I want to print the Object with ID 22, the problem is that I don't have the same structure in each dimension, and it still confuse me..
My Input : 22
My output :
{
ID: 22,
title: 'Arke',
nodes: [{ name: 'Name1' }, { name: 'Name2' }, { name: 'Name3' }],
},
Have you an idea how to edit my function to get my input's response ?
Your recursive function wasn't too far off, you need to check if the item as a tables first before recursively calling it again. And then finally just check the ID in the loop.
eg..
const datas=[{id:1,firstName:"John",tables:[{ID:11,title:"Lorem"},{ID:12,title:"Ipsum"}]},{id:2,firstName:"Doe",tables:[{ID:22,title:"Arke",nodes:[{name:"Name1"},{name:"Name2"},{name:"Name3"}]},{ID:23,title:"Korem"}]},{id:3,firstName:"Brad",tables:[{ID:30,title:"Mern",nodes:[{name:"Name4"},{name:"Name5"},{name:"Name6"}]},{ID:31,title:"Full",nodes:[{name:"Name7"},{name:"Name8"},{name:"Name9"}]}]}];
function findById(arr, ID) {
for (const a of arr) {
if (a.tables) {
const r = findById(a.tables, ID);
if (r) return r;
}
if (a.ID === ID) return a;
}
}
console.log(findById(datas, 22));
if you just need the nested data you can use flatMap and find
const findById = (arr, id) =>
arr
.flatMap(d => d.tables)
.find(t => t.ID === id)
const datas = [{
id: 1,
firstName: 'John',
tables: [{
ID: 11,
title: 'Lorem'
},
{
ID: 12,
title: 'Ipsum'
},
],
},
{
id: 2,
firstName: 'Doe',
tables: [{
ID: 22,
title: 'Arke',
nodes: [{
name: 'Name1'
}, {
name: 'Name2'
}, {
name: 'Name3'
}],
},
{
ID: 23,
title: 'Korem'
},
],
},
{
id: 3,
firstName: 'Brad',
tables: [{
ID: 30,
title: 'Mern',
nodes: [{
name: 'Name4'
}, {
name: 'Name5'
}, {
name: 'Name6'
}],
},
{
ID: 31,
title: 'Full',
nodes: [{
name: 'Name7'
}, {
name: 'Name8'
}, {
name: 'Name9'
}],
},
],
},
];
console.log(findById(datas, 22))
js has amazing array options https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
the ones which will help you most are probably:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
here are some examples
// get the base with id 22
const baseWith22ID = datas.filter(f => f.tables.filter(s => s.id = 22))
// (i guess you want this one) get all elements with id 22
const onlyElementsWith22ID = datas.flatMap(f => f.tables.filter(s => s.id = 22))
First I must say sorry if this question is already answered, but I have not found the answer I am looking for :(
I have an array of objects with unknown nesting depth (it can be 20-30 or even more) and I want to filter it's 'name' property based on input field value.
public nestedArray = [
{id: 1, name: 'Example_1', children: []},
{id: 2, name: 'Test', children: []},
{id: 3, name: 'Test Name', children: [
{id: 10, name: 'Child name', children: [
{id: 20, name: 'Example_14', children: []},
{id: 30, name: 'Last Child', children: []}
]
}
]
}
];
The result I want to receive is an array of objects with only one level deep with 'name' field which includes input value.
For example my input value is 'am', so the result would be:
resultsArray = [
{id: 1, name: 'Example_1'},
{id: 3, name: 'Test Name'},
{id: 10, name: 'Child name'},
{id: 20, name: 'Example_14'}
];
There is no problem to do it on the first level like that:
public filter(array: any[], input_value: string): void {
array = array.filter(el => {
return el.name.toLowerCase().includes(input_value.toLowerCase()));
}
}
Thanks in advance!
You could map the array and their children and take a flat result of objects where the string is matching the name property.
const
find = value => ({ children, ...o }) => [
...(o.name.includes(value) ? [o] : []),
...children.flatMap(find(value))
],
data = [{ id: 1, name: 'Example_1', children: [] }, { id: 2, name: 'Test', children: [] }, { id: 3, name: 'Test Name', children: [{ id: 10, name: 'Child name', children: [{ id: 20, name: 'Example_14', children: [] }, { id: 30, name: 'Last Child', children: [] }] }] }],
result = data.flatMap(find('am'));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another solution with a single result array and a classic approach.
const
find = (array, value) => {
const
iter = array => {
for (const { children, ...o } of array) {
if (o.name.includes(value)) result.push(o);
iter(children);
}
},
result = [];
iter(array);
return result;
},
data = [{ id: 1, name: 'Example_1', children: [] }, { id: 2, name: 'Test', children: [] }, { id: 3, name: 'Test Name', children: [{ id: 10, name: 'Child name', children: [{ id: 20, name: 'Example_14', children: [] }, { id: 30, name: 'Last Child', children: [] }] }] }],
result = find(data, 'am');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Looking for a javascript or typescript solution to turn this array of sql data into a tree structure:
Some other solutions I've tried set the id as a property in the expected array but not the key of the object like in the expected solution.
const sqlData = [
{ id: 1, label: 'root', parentId: 0 },
{ id: 2, label: 'ant', parentId: 1 },
{ id: 3, label: 'cat', parentId: 1 },
{ id: 4, label: 'bear', parentId: 3 },
{ id: 5, label: 'dog', parentId: 3 },
{ id: 6, label: 'elephant', parentId: 5 },
{ id: 7, label: 'frog', parentId: 1 },
];
const expected = [
{
1: {
label: 'root',
children: [
{
2: {
label: 'ant',
children: [],
},
},
{
3: {
label: 'cat',
children: [
{
4: {
label: 'cat',
children: [],
},
},
{
5: {
label: 'dog',
children: [
{
6: {
label: 'elephant',
children: [],
},
},
],
},
},
],
},
},
{
7: {
label: 'frog',
children: [],
},
},
],
},
},
];
This can be done with O(n) time complexity by leveraging object references. By creating a children array on each element and then adding the correct child to its parent's children array, you can accomplish building the whole tree.
const sqlData=[{id:1,label:"root",parentId:0},{id:2,label:"ant",parentId:1},{id:3,label:"cat",parentId:1},{id:4,label:"bear",parentId:3},{id:5,label:"dog",parentId:3},{id:6,label:"elephant",parentId:5},{id:7,label:"frog",parentId:1}];
const parentMap = {};
const root = [];
// Map parent positions
sqlData.forEach((el, i) => {
parentMap[el.id] = i;
el.children = [];
});
sqlData.forEach(({ id, parentId, label, children }) => {
const insert = { [id]: { label, children } };
if (parentId === 0) {
root.push(insert);
return;
}
sqlData[parentMap[parentId]].children.push(insert);
});
console.log(root);
I have an array:
[
{ id: 1,
name: "parent1",
children: [
{ id: 10,
name: "first_child_of_id_1",
children: [
{ id: 100, name: "child_of_id_10", children: []},
{ id: 141, name: "child_of_id_10", children: []},
{ id: 155, name: "child_of_id_10", children: []}
]
},
{ id: 42,
name: "second_child_of_id_1",
children: [
{ id: 122, name: "child_of_id_42", children: []},
{ id: 133, name: "child_of_id_42", children: []},
{ id: 177, name: "child_of_id_42", children: []}
]
}
]
},
{ id: 7,
name: "parent7",
children: [
{ id: 74,
name: "first_child_of_id_7",
children: [
{ id: 700, name: "child_of_id_74", children: []},
{ id: 732, name: "child_of_id_74", children: []},
{ id: 755, name: "child_of_id_74", children: []}
]
},
{ id: 80,
name: "second_child_of_id_7",
children: [
{ id: 22, name: "child_of_id_80", children: []},
{ id: 33, name: "child_of_id_80", children: []},
{ id: 77, name: "child_of_id_80", children: []}
]
}
]
}
]
What I need is an array of arrays like this:
[
[ "id", "name", "parent_id", "parent_name" ],
[ 1, "parent1", null, "" ],
[ 10, "first_child_of_id_1", 1, "parent1"],
[ 42, "second_child_of_id_1", 1, "parent1"],
[100, "child_of_id_10", 10, "first_child_of_id_1"]
]
and so on for all nested objects for me to convert them into CSV rows. I've checked many answers and found a similar problem here: How to convert array of nested objects to CSV?
But it produces too long rows for many nested objects and I am not experienced enough with JavaScript to modify map function.
const categories = [
{ id: 1,
name: "parent1",
children: [
{ id: 10,
name: "first_child_of_id_1",
children: [
{ id: 100, name: "child_of_id_10", children: []},
{ id: 141, name: "child_of_id_10", children: []},
{ id: 155, name: "child_of_id_10", children: []}
]
},
{ id: 42,
name: "second_child_of_id_1",
children: [
{ id: 122, name: "child_of_id_42", children: []},
{ id: 133, name: "child_of_id_42", children: []},
{ id: 177, name: "child_of_id_42", children: []}
]
}
]
},
{ id: 7,
name: "parent7",
children: [
{ id: 74,
name: "first_child_of_id_7",
children: [
{ id: 700, name: "child_of_id_74", children: []},
{ id: 732, name: "child_of_id_74", children: []},
{ id: 755, name: "child_of_id_74", children: []}
]
},
{ id: 80,
name: "second_child_of_id_7",
children: [
{ id: 22, name: "child_of_id_80", children: []},
{ id: 33, name: "child_of_id_80", children: []},
{ id: 77, name: "child_of_id_80", children: []}
]
}
]
}
]
function pivot(arr) {
var mp = new Map();
function setValue(a, path, val) {
if (Object(val) !== val) { // primitive value
var pathStr = path.join('.');
var i = (mp.has(pathStr) ? mp : mp.set(pathStr, mp.size)).get(pathStr);
a[i] = val;
} else {
for (var key in val) {
setValue(a, key == '0' ? path : path.concat(key), val[key]);
}
}
return a;
}
var result = arr.map(obj => setValue([], [], obj));
return [[...mp.keys()], ...result];
}
function toCsv(arr) {
return arr.map(row =>
row.map(val => isNaN(val) ? JSON.stringify(val) : +val).join(',')
).join('\n');
}
<button onclick="console.log(toCsv(pivot(categories)))">Output</button>
Simple DFS or BFS algorithm should do the job here.
The difference is the order of created "rows". If you want to have all children of given node listed immediately after their parent, then you need to use BFS.
Example with DFS and BFS:
const input = [{
id: 1,
name: "parent1",
children: [{
id: 10,
name: "first_child_of_id_1",
children: [{
id: 100,
name: "child_of_id_10",
children: []
},
{
id: 141,
name: "child_of_id_10",
children: []
},
{
id: 155,
name: "child_of_id_10",
children: []
}
]
},
{
id: 42,
name: "second_child_of_id_1",
children: [{
id: 122,
name: "child_of_id_42",
children: []
},
{
id: 133,
name: "child_of_id_42",
children: []
},
{
id: 177,
name: "child_of_id_42",
children: []
}
]
}
]
},
{
id: 7,
name: "parent7",
children: [{
id: 74,
name: "first_child_of_id_7",
children: [{
id: 700,
name: "child_of_id_74",
children: []
},
{
id: 732,
name: "child_of_id_74",
children: []
},
{
id: 755,
name: "child_of_id_74",
children: []
}
]
},
{
id: 80,
name: "second_child_of_id_1",
children: [{
id: 22,
name: "child_of_id_80",
children: []
},
{
id: 33,
name: "child_of_id_80",
children: []
},
{
id: 77,
name: "child_of_id_80",
children: []
}
]
}
]
}
]
//DFS
function deepWalk(node, parent, output = []) {
if (!node || typeof node !== 'object' || !node.id) return;
output.push([node.id, node.name, parent ? parent.id : null, parent ? parent.name : ""])
if (node.children) {
for (const child of node.children) {
deepWalk(child, node, output);
}
}
return output;
}
//BFS
function broadWalk(root) {
const output = []
const queue = [];
queue.push({
node: root,
parent: null
});
while (queue.length) {
const {
node,
parent
} = queue.shift();
output.push([node.id, node.name, parent ? parent.id : null, parent ? parent.name : ""])
if (node.children) {
for (const child of node.children) {
queue.push({
node: child,
parent: node
});
}
}
}
return output;
}
let rowsDfs = [
["id", "name", "parent_id", "parent_name"]
];
let rowsBfs = [
["id", "name", "parent_id", "parent_name"]
];
for (const node of input) {
rowsDfs = [...rowsDfs, ...deepWalk(node)];
rowsBfs = [...rowsBfs, ...broadWalk(node)];
}
console.log("rows DFS: ", rowsDfs)
console.log("rows BFS: ", rowsBfs)
So I have the following Array. I can push to the parent level and child level however I can't seem to push to Children of Children or deeper. Here's the Array:
TREE_DATA: SegmentCategory[] = [
{
id: 1,
name: 'Category 1',
children: [
{
id: 2,
name: 'Category 1-1',
children: [
{
id: 4,
name: 'Category 1-1-a',
children: []
},
{
id: 5,
name: 'Category 1-1-b',
children: []
},
]
},
{
id: 6,
name: 'Category 1-2',
children: [
{
id: 7,
name: 'Category 1-2-a',
children: [
{
id: 8,
name: 'Category 1-2-1',
children: [
{
id: 9,
name: 'Category 1-2-2-a',
children: []
}
]
}
]
}
]
}
]
},
{
id: 10,
name: 'Category 2',
children: []
}
];
And here is the method I am using to add data to the array:
createCategory() {
this.id++;
if (this.formGroup.controls['parentCategory'].value == null) {
this.TREE_DATA.push(
{
id: this.id,
name: this.formGroup.controls['categoryName'].value,
children: []
});
} else {
this.TREE_DATA.forEach((v, index) => {
if (v.id === this.formGroup.controls['parentCategory'].value.id) {
this.TREE_DATA[index].children.push(
{
id: this.id,
name: this.formGroup.controls['categoryName'].value,
children: []
}
);
}
});
}
}
I know that I can just add an additional foreach however this doesn't seem like the best way to handle this, as I could want to go 5-6 layers down at any time to add a new nested child. What would be the best way to handle this?
You could take a recursive function for finding an object in the tree.
function find(array, id) {
var result;
array.some(object => {
if (object.id === id) return result = object;
return result = find(object.children || [], id);
});
return result;
}
var data = [{ id: 1, name: 'Category 1', children: [{ id: 2, name: 'Category 1-1', children: [{ id: 4, name: 'Category 1-1-a', children: [] }, { id: 5, name: 'Category 1-1-b', children: [] }] }, { id: 6, name: 'Category 1-2', children: [{ id: 7, name: 'Category 1-2-a', children: [{ id: 8, name: 'Category 1-2-1', children: [{ id: 9, name: 'Category 1-2-2-a', children: [] }] }] }] }] }, { id: 10, name: 'Category 2', children: [] }];
console.log(find(data, 9));
console.log(find(data, 'unknown'));