Javascript: How to create nested from a linear array based on parent ID [duplicate] - javascript

This question already has answers here:
Recursive depth function on an array
(6 answers)
Closed 1 year ago.
I have an array of categories as:
var categories = [
{
name: 'Books',
slug: 'books',
parent_name: null,
},
{
name: 'Fiction',
slug: 'books/fiction',
parent_name: 'Books'
},
{
name: 'Romance fiction',
slug: 'books/fiction/romance',
parent_name: 'Fiction'
},
{
name: 'Bags',
slug: 'bags',
parent_name: null
}
];
I want to make this array into nested array/object so I can render them in dropdowns menu (upto two levels of dropdowns). But do not have any idea how to do it.
Something like this:
var nestedCatefories = [
{
name: 'Books',
slug: 'books',
parent_name: null,
children: [
{
name: 'Fiction',
slug: 'books/fiction',
parent_name: 'books',
children: [
{
name: 'Romance',
slug: 'books/fiction/romance',
parent_name: 'Fiction'
}
]
}
]
},
{
name: 'Bags',
slug: 'bags',
parent_name: null
}
];
How do I change this linear array to nested array?

Reduce the array to a Map, with a predefined root array. For each new item, add it to the Map using it's name as the key. Then get / initialize it's parent's children array, or use root for items without parents. Add the item to it's parent. At the end get the root that hold the entire tree:
const categories = [{"name":"Books","slug":"books","parent_name":null},{"name":"Fiction","slug":"books/fiction","parent_name":"Books"},{"name":"Romance fiction","slug":"books/fiction/romance","parent_name":"Fiction"},{"name":"Bags","slug":"bags","parent_name":null}];
const result = categories.reduce((acc, item) => {
acc.set(item.name, item)
const parent = item.parent_name === null
? acc.get('root')
: (acc.get(item.parent_name).children ??= [])
parent.push(item)
return acc
}, new Map([['root', []]])).get('root')
console.log(result)

Have you tried using a simple Array.map()?
Something like this:
var nestedArr = linearArr.map(obj=>obj.children= [{obj},{obj}])

Related

Node Js how to fetch data from database in an hierarchical way

I'm writing a back code using NodeJs to fetch some data from backend, I want dataBase data to be like this
like this:
data = [{
name: "Admin",
id: '1',
children: [
{ name: "Admin", id: "1" },
{ name: "groupe1", id: "2" },
{
name: "groupe2", id: "1455", children: [
{ name: "groupe2", id: "1455" },
{ name: "gro", id: "5444" },
{ name: "hhrr", id: "45" }
]
}
]
}]
the idea is simple we have a list of group each group has a parent I want to display all the groups list in an hierarchical way the top one of the tree is done
Some groups are parents and groups in the same time and some others are only groups if the group is not parent we add an object with its name and ID in the array of children of his parent
if this groups is a parent that's mean it has children we add an object with its ID and name in the array of children of his parents, and we add property children for the object which is array named children with for the first time an object with the name and the id of the group etc...
i tryed to do this but it did not work
const getParentsByType = async ({ name, _id }) => {
let parentResult = [
{
id: _id,
name: name,
children: [
{
id: _id,
name: name,
},
],
},
];
parentResult= await findParent(_id, parentResult[0].children, 0);
return parentResult;
};
const findParent = async (parentId, parentResult, itemPos) => {
let children = await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id');
for (let i = 0; i < children.length; i++) {
let childrenList = await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id');
if (childrenList.length != 0) {
parentResult.push(buildParentWithChild(children[i]._id, children[i].name));
findParent(children[i]._id,parentResult.children[i],itemPos++)
} else {
parentResult.push(buildParent(children[i]._id, children[i].name));
}
}
return parentResult
};
and this the model of the data base
const Group = mongoose.Schema({
name: {
type: String,
required: true,
},
status: {
type: Boolean,
required: true,
},
parent: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Group',
},
});
i had two days trying to resolve tis but with no result
i need some helps and Thank you
Try parsing your returned data. It validates your data as objects i dont see any problem with your function regardless i still have no idea what format your a trying to build.
let children = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id')));
let childrenList = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id')));
If I understand you right, you want to convert the array returned by Models.GroupModel.find, and which looks like
var dbresult = [
{_id: "1", parent: null, name: "one"},
{_id: "2", parent: "1", name: "two"}
];
into a hierarchical structure. This can be done with a function that adds all children of a given parent p, including, recursively, their children. Like the following:
function children(p) {
var result = [];
for (r of dbresult) if (r.parent === p) {
var row = {_id: r._id, name: r.name};
var chld = children(r._id);
if (chld.length > 0) row.children = chld;
result.push(row);
}
return result;
}
console.log(JSON.stringify(children(null)));
Note that this approach requires only one database access (to fill the dbresult) and is therefore probably faster than your findParent function.

How to update a JavaScript object with nested array and objects under n levels?

Following the below tree
let dumbTree = {
name: "vob",
uuid: "edbf146d-c9ee-4568-8c1e-a095f8ad4aff",
children: [
{
name: "A0A",
uuid: "8655460b-4862-4d11-800a-870482f4701b",
nodeType: "project",
children: []
},
{
name: "A1A",
uuid: "8655460b-4862-4d11-800a-870482f4701b",
nodeType: "project",
children: []
}
]
}
I'd like to convert incrementally path into the same kind of structure.
In that example, I've got a path /vob/A0A, and /vob/A1A, and then if I'm clicking on the leaf A1A, it'd give me an array like from an API call:
['/vob/A1A/A1A111', '/vob/A1A/A1A112', '/vob/A1A/A1A113'], so then I'd like to populate the tree accordingly
dumbTree = {
name: "vob",
uuid: "edbf146d-c9ee-4568-8c1e-a095f8ad4aff",
children: [
{
name: "A0A",
uuid: "8655460b-4862-4d11-800a-870482f4701b",
nodeType: "project",
children: []
},
{
name: "A1A",
uuid: "8655460b-4862-4d11-800a-870482f4701c",
nodeType: "project",
children: [
{
name: 'A1A111',
children: []
},
{
name: 'A1A112',
children: []
}
...
]
}
]
}
And then clicking on A1AAAA would give other children that will replace the empty array in A1A sub object, etc...
Is there any efficient way to do it?
I tried something with multiple finds on nested children, but this approach isn't going to be very effective on n levels.
const testChildren2 = [
{
name: "AAA123",
uuid: "00214ef8-fb29-4326-b236-3c90ab47eaaf",
children: []
}
]
let dumbTreeIndexA = dumbTree.children.findIndex((elt) => elt.name === 'AAA')
dumbTree.children[dumbTreeIndexA].children = testChildren2
// vobs/AAA/AAA123
const studyIndex = dumbTree.children[dumbTreeIndexA].children.findIndex((elt) => elt.name === 'AAA123')
I'm open to existing libraries such as lodash, ramdaJS, but didn't find a good way to do this yet.

Filter an array of objects with a second array with multiple values

I am trying to write a function to take the first object in the "parent" array, pull out the child field (which is in that array) and use that field to filter the second object called "child".
I want to get all the related records from the child object that are in the child field in the parent object.
Expected output
child: [
{
**id: 1,**
name: 'Jimmy Yukka',
},
{
**id: 2,**
name: 'Up North',
}
INPUT
Parent: [
{
**id: 1,**
name: 'Melbourne Bands',
**child: [1, 2]**
}
I have the following data
Parent: [
{
**id: 1,**
name: 'Melbourne Bands',
**child: [1, 2]**
},
{
id: 2,
name: 'Sydney Bands',
child: [3]
}
],
child: [
{
**id: 1,**
name: 'Jimmy Yukka',
},
{
**id: 2,**
name: 'Up North',
},
{
id: 3,
url: 'jimmyyukka.com',
name: 'INXS',
CreatedByUserId: 1
}
],
The code of the function I have implemented so far:
currentChildrenIds(ParentId, parentData, childData) {
const singleParentRecord = parentData.filter(function(parent) {
return parent.id === ParentId;
});
const parentsChildIds = singleParentRecord[0].books;
const childRecords = childData.filter(function(child) {
return child.id === parentsChildIds
});
return childRecords
}
NOTES
This bit here is where it is wrong
const childRecords = childData.filter(function(child) {
return child.id === parentsChildIds
This bit here is also a bit rubbish (hardcoding the [0])but not I'm not sure how I should be coding it correctly
const parentsChildIds = singleParentRecord[0].books;
here,
const childRecords = childData.filter(function(child) {
return child.id === parentsChildIds
parentsChildIds is a reference to an array: you don't want to test if an id is === to a a reference,
You have to be explicit and check if the id is contained in the array:
const childRecords = childData.filter(function(child) {
return parentsChildIds.includes(child.id)
Regarding the singleParentRecord[0] that does feel weird,
since you know the method filter will always return an array of size 1 or 0,
you can use the method find instead of filter
Also in functionnal programming (array functions such as filter, map, find...)
I advice you to read a bit about the arrow function syntax because:
The syntex is more dense and it makes it easier for your brain to understand when several functions are chained
If you want to use variables which are defined outside of the function it will be available only inside of an arrow function
your code with an arrow function:
const childRecords = childData.filter((child) => {
return child.id === parentsChildIds
}
Try this:
const Parent = [
{
id: 1,
name: 'Melbourne Bands',
child: [1, 2]
},
{
id: 2,
name: 'Sydney Bands',
child: [3]
}
];
const children = [
{
id: 1,
name: 'Jimmy Yukka',
},
{
id: 2,
name: 'Up North',
},
{
id: 3,
url: 'jimmyyukka.com',
name: 'INXS',
CreatedByUserId: 1
}
];
// We create a new array with Array.map
const result = Parent.map(parent => ({
// Spread properties of the parent
...parent,
// Override the child property and filter the children array with the `includes` method
child: children.filter(child => parent.child.includes(child.id)),
}))
console.log(result);

how to loop through multiple arrays inside an array and filter a value from it-Javascript

I'm using EXTJS framework for my code.
below is my array structure:
data = [{
id: 22,
rows: [{
id: "673627",
name: "ABS",
address: "536street"
}, {
id: "333",
name: "TEST$$",
address: "536street"
}, {
id: "999",
name: "TEST$$",
address: "536street"
}]
}, {
id: 33,
rows: [{
id: "899",
name: "TES",
address: "536street"
}, {
id: "333",
name: "TEST$$",
address: "536street"
}, {
id: "999",
name: "TES673",
address: "536street"
}]
}]
Now I want to filter the name from this array, whose value I'm comparing with say "TEST$$".
I'm doing this;
Ext.each(data, function(item) {
filter = item.rows.filter(function(name) {
return name.name === "TEST$$";
}, this);
}, this);
console.log(filter);
In this case, it returns only 1 match, where as I have 3 matches for this particular value. It returns the match from the last item in the data array and hence I dont get all the matching values, any idea how this can be looped to get all values matching?
thx!
You're reassigning the filter variable on every iteration over the data array:
filter = item.rows.filter(function(name) {
return name.name === "TEST$$";
}, this);
On the last iteration, there is only one match, the one with id of 333, so that's the only one that you see after running the Ext.each. Try pushing to an external array that doesn't get overwritten instead:
const testItems = [];
Ext.each(data, function(item) {
const filtered = item.rows.filter(row => row.name === "TEST$$")
testItems.push(...filtered);
});
console.log(testItems);
Note that there's no need to pass along the this context.
Another option is to flatMap to extract all rows to a single array first:
const output = data
.flatMap(({ rows }) => rows)
.filter(({ name }) => name === 'TEST$$');

How to build tree array from flat array of object with category and subCategrie properties

I am trying to build tree array from flat array, each item in the flat array has two property need to be used to build the tree array, they are 1. category. 2. subCategrie which is array of string.
let data = [
{
id: 1,
name: "Zend",
category: "php",
subCategory: ["framework"]
},
{
id: 2,
name: "Laravel",
category: "php",
subCategory: ["framework"]
},
{
id: 3,
name: "Vesion 5",
category: "php",
subCategory: ["versions"]
},
{
id: 4,
name: "Angular",
category: "frontend",
subCategory: ["framework", "typescript"]
},
{
id: 5,
name: "Aurelia",
category: "frontend",
subCategory: ["framework", "typescript"]
},
{
id: 6,
name: "JQuery",
category: "frontend",
subCategory: []
}
];
It should be
let tree = [
{
name: "php",
children: [
{
name: "framework",
children: [
{
id: 1,
name: "Zend"
},
{
id: 2,
name: "Laravel"
}
]
},
{
name: "versions",
children: [
{
id: 3,
name: "Vesion 5"
}
]
}
]
}
// ...
];
Is there any article, link solving similar problem?
I gave it many tries but stuck when trying to build the sub categories children.
Here's my last attempt which throws error and I know it's wrong but it's for the ones who want to see my attempts
const list = require('./filter.json')
let tree = {};
for (let filter of list) {
if (tree[filter.category]) {
tree[filter.category].push(filter);
} else {
tree[filter.category] = [filter];
}
}
function buildChildren(list, subcategories, category, index) {
let tree = {}
for (let filter of list) {
if (filter.subcategory.length) {
for (let i = 0; i < filter.subcategory.length; i++) {
let branch = list.filter(item => item.subcategory[i] === filter.subcategory[i]);
branch.forEach(item =>{
if (tree[filter.subcategory[i]]){
tree[filter.subcategory[i]] = tree[filter.subcategory[i]].push(item)
}else{
tree[item.subcategory[i]] = [item]
}
})
}
}
}
console.log('tree ', tree);
}
Heads up, For javascript I usually use Lodash (usually written as _ in code) but most of these methods should also be built in to the objects in javascript (i.e. _.forEach = Array.forEach())
const tree = [];
// First Group all elements of the same category (PHP, Frontend, etc.)
data = _.groupBy(data, 'category');
_.forEach(data, function (categoryElements, categoryName) {
// Each Category will have it's own subCategories that we will want to handle
let categorySubCategories = {};
// The categoryElements will be an array of all the objects in a given category (php / frontend / etc..)
categoryElements.map(function (element) {
// For each of these categoryies, we will want to grab the subcategories they belong to
element.subCategory.map(function (subCategoryName) {
// Check if teh category (PHP) already has already started a group of this subcategory,
// else initialize it as an empty list
if (!categorySubCategories[subCategoryName]) { categorySubCategories[subCategoryName] = []; }
// Push this element into the subcategory list
categorySubCategories[subCategoryName].push({id: element.id, name: element.name});
});
});
// Create a category map, which will be a list in the format {name, children}, created from
// our categorySubCategories object, which is in the format {name: children}
let categoryMap = [];
_.forEach(categorySubCategories, function (subCategoryElements, subCategoryName) {
categoryMap.push({name: subCategoryName, children: subCategoryElements});
});
// Now that we've grouped the sub categories, just give the tree it's category name and children
tree.push({name: categoryName, children: categoryMap});
});
};
The key to success here is to create an interim format that allows for easy lookups. Because you work with children arrays, you end up having to use filter and find whenever you add something new, to prevent duplicates and ensure grouping.
By working with a format based on objects and keys, it's much easier to do the grouping.
We can create the groups in a single nested loop, which means we only touch each item once for the main logic. The group has this format:
{ "categoryName": { "subCategoryName": [ { id, name } ] } }
Then, getting to the required { name, children } format is a matter of one more loop over the entries of this tree. In this loop we move from { "categoryName": catData } to { name: "categoryName", children: catData }
Here's an example that shows the two steps separately:
const data=[{id:1,name:"Zend",category:"php",subCategory:["framework"]},{id:2,name:"Laravel",category:"php",subCategory:["framework"]},{id:3,name:"Vesion 5",category:"php",subCategory:["versions"]},{id:4,name:"Angular",category:"frontend",subCategory:["framework","typescript"]},{id:5,name:"Aurelia",category:"frontend",subCategory:["framework","typescript"]},{id:6,name:"JQuery",category:"frontend",subCategory:[]}];
// { category: { subCategory: [ items ] } }
const categoryOverview = data.reduce(
(acc, { id, name, category, subCategory }) => {
// Create a top level group if there isn't one yet
if (!acc[category]) acc[category] = {};
subCategory.forEach(sc => {
// Create an array for this subCat if there isn't one yet
acc[category][sc] = (acc[category][sc] || [])
// and add the current item to it
.concat({ id, name });
});
return acc;
},
{}
)
const nameChildrenMap = Object
.entries(categoryOverview)
// Create top level { name, children } objects
.map(([cat, subCats]) => ({
name: cat,
children: Object
.entries(subCats)
// Create sub level { name, children } objects
.map(([subCat, items]) => ({
name: subCat,
children: items
}))
}))
console.log(nameChildrenMap);

Categories

Resources