Filter with different levels of depth - javascript

I have a menu with this structure
item1
item2
childrenOfItem2
childrenOfchildren1
childrenOfchildren2
HELLOchildrenOfchildren3
childrenOfItem2
childrenOfItem2
HELLOitem3
item4
childrenOfItem4
HELLOchildrenOfItem4
item5
childrenOfItem5
So, Id' like to get all the items that have the word "HELLO" and what I'm doing is a loop over the first items, then, another loop and then another loop, is there any other way of doing it? Since let's say that if we add another level of depth in the menu it will not work,
Thank you!
Edited: adding JS for better understanding
const matchName = (item, word) =>
item?.title?.toLowerCase()?.includes(word?.toLowerCase());
const filter = (word = "", arr = []) => {
const listOfItems = [];
arr.forEach((item) => {
if (matchName(item, word)) {
listOfItems.push(item);
} else if (item?.children?.length > 0) {
const newSubItem = [];
item.children.forEach((subItem) => {
if (matchName(subItem, word)) {
newSubItem.push(subItem);
} else if (subItem?.children?.length > 0) {
const newSubSubItems = [];
subItem.children.forEach((subsubItem) => {
if (matchName(subsubItem, word)) {
newSubSubItems.push(subsubItem);
}
});
if (newSubSubItems?.length > 0) {
newSubItem.push({ ...subItem, children: newSubSubItems });
}
}
});
if (newSubItem?.length > 0) {
listOfItems.push({ ...item, children: newSubItem });
}
}
});
return listOfItems;
};
Sample of arr received as parameter in the fnc:
const list = [
{
id: "41",
title: "sample",
children: [
{
id: "42",
title: "sample",
children: [
{
id: "43",
title: "sample",
children: [],
},
{
id: "44",
title: "sample",
children: [],
},
{
id: "45",
title: "sample",
children: [],
},
],
},
{
id: "46",
title: "sample",
children: [
{
id: "47",
title: "sample",
children: [],
},
{
id: "48",
title: "sample",
children: [],
},
],
},
],
},
{
id: "29",
title: "sample",
children: [
{
id: "30",
title: "sample",
children: [],
},
{
id: "49",
title: "sample",
children: [],
},
{
id: "31",
title: "sample",
children: [],
},
],
},
];

If you really don't want to change your structure and flatten your list, you can loop them dynamically, just use a function and call it within itself.
Here's an example using the const list you provided:
let found = false,
loop = (items, filter) => {
found = false;
let results = items.filter(a => filter(a));
if(results.length > 0) {
found = true;
return results;
}
if(!found && items && items.length > 0) {
for(let i = 0; i < items.length && !found; i++) {
if(items[i] && items[i].children && items[i].children.length > 0)
results = loop(items[i].children, filter);
}
}
return results;
};
let items = loop(list, item => item.id && item.id == "48");
In the example above we filter the list dynamically, iterating each and every item in the list and return the first item we find that matches a provided filter (2nd parameter).
You can use this to do pretty much whatever you'd like and can add arguments to add the menu "level" you're currently on, the parents, etc.
Note that this might get a bit slow if the list goes very deep, wrote it quickly and didn't tested outside of your provided list.
Ideally I would change the structure in order to make it easier to work with but if you must keep it this way, this works.

You could find the object (without children) and get a flat array.
const
find = (array, title) => array.flatMap(({ children, ...o }) => [
...(o.title.includes(title) ? [o]: []),
...find(children, title)
]),
list = [{ id: "41", title: "sample", children: [{ id: "42", title: "sample", children: [{ id: "43", title: "sample", children: [] }, { id: "44", title: "sample", children: [] }, { id: "45", title: "sample", children: [] }] }, { id: "46", title: "no sample", children: [{ id: "47", title: "sample", children: [] }, { id: "48", title: "sample", children: [] }] }] }, { id: "29", title: "sample", children: [{ id: "30", title: "sample", children: [] }, { id: "49", title: "no sample", children: [] }, { id: "31", title: "sample", children: [] }] }];
console.log(find(list, 'no sample'));
console.log(find(list, 'ample'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

How to sort nested array object using another nested array of on the basis of id?

My objective here is to convert the input array into the structure of the output array.
The input array and output array are shown below. If you observe carefully we can see that id is common in both the arrays and only title changes.
var output = [{
id: "1",
title: 'title',
children: [],
},
{
id: "2",
title: 'title2',
children: [],
},
{
id: "3",
title: 'title3',
children: [{
id: "4",
title: 'title4',
children: [],
}, {
id: "5",
title: 'title5',
children: [{
id: "6",
title: 'title6',
children: [],
}, {
id: "7",
title: 'title7',
children: [],
}, {
id: "9",
title: 'title9',
children: [],
}]
}],
}
]
var input = [{
id: "1",
title: 'title_chnaged',
children: [],
},
{
id: "2",
title: 'title_changed',
children: []
},
{
id: "3",
title: 'title_changed',
children: [{
id: "4",
title: 'title_changed',
children: [],
}, {
id: "5",
title: 'title_changed',
children: [],
children: [{
id: "6",
title: 'title_changed',
children: [],
},
{
id: "7",
title: 'title_changed',
children: [],
}
]
}],
},
{
id: "9",
title: 'title_chnaged',
children: [],
}
]
This function will look into the input array of corresponding element of output array on the . basis of id
let found;
function findTheKey(id, widget) {
let newObj = [...widget];
for (var key in newObj) {
if (newObj[key]["id"] == id) {
found = newObj[key];
break;
}
if (newObj[key].hasOwnProperty("children")) {
findTheKey(id, newObj[key].children);
}
}
return found;
}
This function will iterate over the output array and look for corresponding element in input array
function findAllObjectOnArray(output) {
let newObj = [...output];
for (let key in newObj) {
newObj[key] = {
...findTheKey(newObj[key]['id'], input),
children: newObj[key].children
};
if (newObj[key].hasOwnProperty("children")) {
findAllObjectOnArray(newObj[key].children, input);
}
}
return newObj;
}
var result = findAllObjectOnArray(output)
console.log(result)
The result is as expected on label 1 but as we move into nested object, it didn't changed.
Please suggest me something which will let it work. Any hint or solution is highly welcome ?
I've created a function fn that maps array elements to elemnts with property title = "title"+id and then recursively maps children nodes.
var output = [
{
id: "1",
title: 'title',
children: [],
},
{
id: "2",
title: 'title2',
children: [],
},
{
id: "3",
title: 'title3',
children: [
{
id: "4",
title: 'title4',
children: [],
},
{
id: "5",
title: 'title5',
children: [
{
id: "6",
title: 'title6',
children: [],
},
{
id: "7",
title: 'title7',
children: [],
},
{
id: "9",
title: 'title9',
children: [],
}]
}],
}]
var input = [
{
id: "1",
title: 'title_chnaged',
children: [],
},
{
id: "2",
title: 'title_changed',
children: []
},
{
id: "3",
title: 'title_changed',
children: [
{
id: "4",
title: 'title_changed',
children: [],
},
{
id: "5",
title: 'title_changed',
children: [],
children: [
{
id: "6",
title: 'title_changed',
children: [],
},
{
id: "7",
title: 'title_changed',
children: [],
}]
}],
},
{
id: "9",
title: 'title_chnaged',
children: [],
}]
var fn = node =>
node && node.map( x =>
({
...x,
title: "title"+x.id,
children: fn(x.children)
})
)
var result = fn(output)
console.log(result)
A tip: Either stick to immutable (copy all objects) or modify it in-place. Things become really complicated when you mix the two unless you know what you are doing.
I'm not entirely sure if this is what you mean? It's hard to be sure if it works correctly because the title_changed strings are all the same.
I just added newObj[key].children = findAllObjectOnArray(newObj[key].children); so that the children property gets updated correctly (since you make a clone of the array on every call, so children doesn't get updated when you try to modify it by reference).
var output = [
{
id: "1",
title: 'title',
children: [],
},
{
id: "2",
title: 'title2',
children: [],
},
{
id: "3",
title: 'title3',
children: [
{
id: "4",
title: 'title4',
children: [],
},
{
id: "5",
title: 'title5',
children: [
{
id: "6",
title: 'title6',
children: [],
},
{
id: "7",
title: 'title7',
children: [],
},
{
id: "9",
title: 'title9',
children: [],
}]
}],
}]
var input = [
{
id: "1",
title: 'title_chnaged',
children: [],
},
{
id: "2",
title: 'title_changed',
children: []
},
{
id: "3",
title: 'title_changed',
children: [
{
id: "4",
title: 'title_changed',
children: [],
},
{
id: "5",
title: 'title_changed',
children: [],
children: [
{
id: "6",
title: 'title_changed',
children: [],
},
{
id: "7",
title: 'title_changed',
children: [],
}]
}],
},
{
id: "9",
title: 'title_chnaged',
children: [],
}]
let found;
function findTheKey(id, widget) {
let newObj = [...widget];
for (var key in newObj) {
if (newObj[key]["id"] == id) {
found = newObj[key];
break;
}
if (newObj[key].hasOwnProperty("children")) {
findTheKey(id, newObj[key].children);
}
}
return found;
}
function findAllObjectOnArray(output) {
let newObj = [...output];
for (let key in newObj) {
newObj[key] = {
...findTheKey(newObj[key]['id'], input),
children: newObj[key].children
};
if (newObj[key].hasOwnProperty("children")) {
newObj[key].children = findAllObjectOnArray(newObj[key].children);
}
}
return newObj;
}
var result = findAllObjectOnArray(output)
console.log(result)

Why code after return statement executing inside for-in loop in javascript?

my variable look like this
var widgets2 = [{
id: "1",
title: 'title',
children: [],
},
{
id: "2",
title: 'title2',
children: []
},
{
id: "3",
title: 'title3',
children: [{
id: "4",
title: 'title4',
children: [],
}, {
id: "5",
title: 'title5',
children: [],
children: [{
id: "6",
title: 'title6',
children: [],
},
{
id: "7",
title: 'title7',
children: [],
}
]
}],
},
{
id: "9",
title: 'title9',
children: [],
}
]
The function code look like this
function findTheKey(id,widget){
let newObj=[...widget];
for(var key in newObj){
if(newObj[key]['id']==id){
console.log(newObj[key])
return newObj[key];
}
console.log("came here")
if(newObj[key].hasOwnProperty("children")){
findTheKey(id,newObj[key].children);
}
}
}
When the called the function using following code
var result=findTheKey(4,widgets2);
console.log(result)
The result look like this
{id: "4", title: "title4", children: Array(0)}
came here
That means even after executing return statement, console.log getting executed, any help will be highly appreciated.
Thankyou
Since this is a recursive function, the return does not have the effect you expect, you need a variable outside the recursive function to keep the current state of what you want to find.
See the snippet below for example:
var widgets2 = [
{
id: "1",
title: "title",
children: [],
},
{
id: "2",
title: "title2",
children: [],
},
{
id: "3",
title: "title3",
children: [
{
id: "4",
title: "title4",
children: [],
},
{
id: "5",
title: "title5",
children: [],
children: [
{
id: "6",
title: "title6",
children: [],
},
{
id: "7",
title: "title7",
children: [],
},
],
},
],
},
{
id: "9",
title: "title9",
children: [],
},
];
let found;
function findTheKey(id, widget) {
let newObj = [...widget];
for (var key in newObj) {
if (newObj[key]["id"] == id) {
found = newObj[key];
break;
}
if (newObj[key].hasOwnProperty("children")) {
findTheKey(id, newObj[key].children);
}
}
return found;
}
var result = findTheKey(4, widgets2);
console.log(result);
You should update your question for this, but since you asked for it in the comments, here is what I propose.
function findTheKey(id, widget) {
const newObj = [...widget];
for (const key in newObj) {
if (newObj[key]["id"] === `${id}`) {
return newObj[key];
}
if (newObj[key].hasOwnProperty('children')) {
/* Use the result of the recursive function */
const foundObject = findTheKey(id, newObj[key].children);
if(foundObject) return foundObject;
}
}
return false;
}

Add child or remove item by path from tree like array of objects

I have nested array of objects like
let treeArr =
{
name: "Top Level", id:'a12',
children: [
{
name: "Level 2: A", id:'a',
children: [
{ name: "Daughter of A", id: 'a',
children: [
{ name: "Another Sub0 Issuer", id: '504' },
{ name: "Another Sub1 Issuer", id: '109' },
{ name: "Another Sub2 Issuer", id: '209' },
]
},
{ name: "Daughter of A", id: '165' },
]
},
{
name: "ABC Co LLC", id:'1234',
children: [
{ name: "Daughter of A", id: 'a' },
{ name: "Daughter of A", id: 'x' },
{ name: "Daughter of Y", id: 'a',
children:[
{ name: "Another Suba Issuer", id: '219' },
{ name: "Another Subb Issuer", id: '409',
children:[
{ name: "Another 4th Issuer", id: '200' },
{ name: "Another 4th Issuer", id: '300' },
{ name: "Another 4th Issuer", id: '400' },
]
},
{ name: "Another Suba Issuer", id: '479' },
]
}
]
}
]
}
function findIndexNested(data, id) {
if (data.id === id) return [];
let result;
const i = (data.children || []).findIndex(child => {
return result = findIndexNested(child, id)
});
if (result) return [i, ...result];
}
function findByPath(data, path) {
for (let i of path) data = data.children[i];
return data
}
I need to delete or add children to certain children items to/from treeArr,
my finder method returns index of searched item, for example: [0, 2, 0, 1]
first children of third children of first ...
so i need to generate this as code dynamically instead hardcoded
my current ugly solution is looks like below, path returns from findIndexNested(.,.)
treeArr.children[path[0]].children[path[1]].children[path[2]].children[path[3]]
how can I add child or remove found item from treeArr ?
function removebyPath(obj, path, i, len ) {
if(len===i+1){ delete obj.children[path[i]]; return;}
removebyPath(obj.children[path[i]],path, i+1, len );
}
let path = findIndexNested(obj, '219' );
removebyPath(obj, path, 0, path.length);
console.log('result:', obj );
removebyPath method removes item from passed path

filter is not working for third child level (nested level)

I'm trying to filter an array having nested level of 3.. I have to filter this array at the last level.
array = [{
children: [{
children: [{
children: [],
id: 2694,
name: "Some Random data"
}, {
children: [],
id: 2695,
name: "Another Random Data"
}],
id: 2574,
name: "Test data",
}],
id: 2530,
name: "Main Test data"
}, {
children: [{
children: [{
children: [],
id: 2696,
name: "Secondary test Data"
}, {
children: [],
id: -1,
name: "Random Text"
}],
id: 2575,
name: "Another random Text"
}],
id: 2531,
name: "New Data"
}]
I have tried this function
function(random){
let array3=[];
this.array.forEach(cap=>{
let tempparent={...cap};
let child1= tempparent.children.forEach(ch=>{
let tempfeat={...ch};
let tempchildren = tempfeat.children.filter(fe=>{
if(fe.id!=random.id){
return fe
}
});
// console.log(tempchildren)
tempfeat.children = tempchildren;
// console.log(tempfeat.children)
});
console.log(child1)
tempparent.children= child1;
console.log(tempparent.children)
nodes3.push(tempparent)
})
this.array= array3
console.log(this.array);
}
I want to filter it at third level using id value. when the id matches the matched object must be removed.
You could take a dynamic approach and take the children out of the object, check the id and if found, ignore the object.
Otherwise take the children and get a subset with a recursive call and rebuild a new object and push this one to the result set.
This approach does not mutate the original data, but returns all new objects and works for an arbitrary count of levels.
function remove(array, id) {
return array.reduce((r, { children, ...o }) => {
if (o.id === id) return r;
children = remove(children || [], id);
if (children.length) o.children = children;
r.push(o);
return r;
}, []);
}
var data = [{ children: [{ children: [{ children: [], id: 2694, name: "Some Random data" }, { children: [], id: 2695, name: "Another Random Data" }], id: 2574, name: "Test data", }], id: 2530, name: "Main Test data" }, { children: [{ children: [{ children: [], id: 2696, name: "Secondary test Data" }, { children: [], id: -1, name: "Random Text" }], id: 2575, name: "Another random Text" }], id: 2531, name: "New Data" }],
result = remove(data, 2574);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use recursive function.
For example, like this
function removeById(_id,arr){
if(arr.id === _id){
return true;
}else{
arr.children.forEach(currentItem => {
if(getById(_id,currentItem)){
arr.children = arr.children.filter(x=>x.id !== _id);
}
});
}
}
and using this function
removeById(2694,array[0]);
removeById(2694,array[1]);
please examine example

looping through a json tree object and create new array

Is there a way good way JS/ES6 to loop through an object and it's children and creating new object tree array.
I have this json tree object:
[
{
id: "001",
deparmentsIds: [
"002",
"003"
],
details: {
parentDeparmentsId: null,
name: "Top"
}
},
{
id: "002",
deparmentsIds:[
"004"
],
details: {
parentDeparmentsId: ["001"],
name: "Operations"
}
},
{
id: "003",
deparmentsIds:[]
details: {
parentDeparmentsId: ["001"],
name: "Support"
}
},
{
id: "004",
deparmentsIds:[]
details: {
parentDeparmentsId: ["002"],
name: "Support operations"
}
}
]
I want to create new object array tree that looks like this:
You could create recursive function with reduce and map method to create nested object structure.
const data = [{"id":"001","deparmentsIds":["002","003"],"details":{"parentDeparmentsId":null,"name":"Top"}},{"id":"002","deparmentsIds":["004"],"details":{"parentDeparmentsId":"001","name":"Operations"}},{"id":"003","deparmentsIds":[],"details":{"parentDeparmentsId":"001","name":"Support"}},{"id":"004","deparmentsIds":[],"details":{"parentDeparmentsId":"002","name":"Support operations"}}]
function tree(input, parentId) {
return input.reduce((r, e) => {
if (e.id == parentId || parentId == undefined && e.details.parentDeparmentsId == null) {
const children = [].concat(...e.deparmentsIds.map(id => tree(input, id)))
const obj = {
[e.details.name]: children
}
r.push(obj)
}
return r;
}, [])
}
const result = tree(data)
console.log(result)
You could collect all information in an object with a single loop and return only the nodes with no parent.
function getTree(data, root) {
var o = {};
data.forEach(({ id, details: { parentDeparmentsId: parent, name } }) => {
var temp = { id, name };
if (o[id] && o[id].children) {
temp.children = o[id].children;
}
o[id] = temp;
o[parent] = o[parent] || {};
o[parent].children = o[parent].children || [];
o[parent].children.push(temp);
});
return o[root].children;
}
var data = [{ id: "001", deparmentsIds: ["002", "003"], details: { parentDeparmentsId: null, name: "Top" } }, { id: "002", deparmentsIds: ["004"], details: { parentDeparmentsId: ["001"], name: "Operations" } }, { id: "003", deparmentsIds: [], details: { parentDeparmentsId: ["001"], name: "Support" } }, { id: "004", deparmentsIds: [], details: { parentDeparmentsId: ["002"], name: "Support operations" } }],
tree = getTree(data, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories