Filter nested object and keep parents - javascript

I want to search an nested object by values of property 'name' and the result will keep its all parents.
For example,
const object = [
{
name: 'Mary',
children: [
{
name: 'Jack',
},
{
name: 'Kevin',
children: [
{
name: 'Lisa',
}
]
}
]
},
{
name: 'Gina',
children: [
{
name: 'Jack',
}
]
}
]
If I search 'Mary', it should be return:
[
{
name: 'Mary',
}
]
If I search 'Jack', it should be return:
[
{
name: 'Mary',
children: [
{
name: 'Jack',
}
]
},
{
name: 'Gina',
children: [
{
name: 'Jack',
}
]
}
]
If I search 'Lisa', it should be return:
[
{
name: 'Mary',
children: [
{
name: 'Jack',
children: [
{
name: 'Lisa',
}
]
}
]
}
]
I tried some methods but I could only filter two layer. As below:
return object.filter(data => {
if (data.children) {
return data.name.includes(keyword) || data.children.find(item => item.name.includes(keyword));
}
return data.name.includes(keyword);
})
Could someone point me in the right direction? Thanks!

You could build an object and if nested, check the children and create the parents, if necessary.
function getObjects(array, target) {
return array.reduce((r, { name, children = [] }) => {
if (name === target) {
r.push({ name });
return r;
}
children = getObjects(children, target);
if (children.length) {
r.push({ name, children })
}
return r;
}, []);
}
var data = [{ name: 'Mary', children: [{ name: 'Jack' }, { name: 'Kevin', children: [{ name: 'Lisa' }] }] }, { name: 'Gina', children: [{ name: 'Jack' }] }];
console.log(getObjects(data, 'Mary'));
console.log(getObjects(data, 'Jack'));
console.log(getObjects(data, 'Lisa'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Here is an example of a depth-first approach:
function searchWithParents(tree, query) {
let results = [];
for (const {name, children} of tree) {
if (name === query) {
results.push({name});
}
if (children) {
const subtreeResults = searchWithParents(children, query);
const mappedResults = subtreeResults.map(child => ({name, children: [child]}))
results = results.concat(mappedResults);
}
}
return results;
}
console.log(searchWithParents(object, 'Mary'));
console.log(searchWithParents(object, 'Jack'));
console.log(searchWithParents(object, 'Lisa'));

Related

Grouping Arrays By Nested Arrays

I have the following array that I'd like to transform into an Object with unique hobbies as the keys
const arr = [
{ name: 'Joe', hobbies: ['skating', 'biking', 'music'] },
{ name: 'Kim', hobbies: ['fishing', 'biking', 'karate'] },
{ name: 'Ben', hobbies: ['surfing'] },
]
I use lodash's handy groupBy function but it groups the multiple array elements into single keys like so
{
'skating,biking,music': [
{ name: 'Joe' }
],
'fishing,biking,karate': [
{ name: 'Kim' }
],
'surfing': [
{ name: 'Ben' }
],
}
What I need is the following output (note the objects are repeated for each of their respective hobbies)
{
biking: [
{ name: 'Joe' },
{ name: 'Kim' }
],
skating: [
{ name: 'Joe' }
],
karate: [
{ name: 'Kim' }
],
surfing: [
{ name: 'Ben' }
],
...
}
Is there a simple way to group this array without looping through each array element, splitting them up and regrouping? Would like to avoid this if there's better utility method out there I'm unaware of
You can iterate each item and each hobbie and then add it to a result object:
const arr = [
{ name: 'Joe', hobbies: ['skating', 'biking', 'music'] },
{ name: 'Kim', hobbies: ['fishing', 'biking', 'karate'] },
{ name: 'Ben', hobbies: ['surfing'] }
]
const result = {};
arr.forEach(item =>
item.hobbies.forEach(hobbie =>
result[hobbie] = (result[hobbie] || []).concat({name: item.name})
)
)
console.log(result);
const arr = [
{ name: 'Joe', hobbies: ['skating', 'biking', 'music'] },
{ name: 'Kim', hobbies: ['fishing', 'biking', 'karate'] },
{ name: 'Ben', hobbies: ['surfing'] }
]
const result = {};
arr.forEach(item =>
item.hobbies.forEach(hobbie =>
result[hobbie] = result[hobbie]?[...result[hobbie],{name: item.name}]: [{name: item.name}]
)
)
console.log(result);
I've renamed arr to people for better understanding.
const people = [
{ name: 'Joe', hobbies: ['skating', 'biking', 'music'] },
{ name: 'Kim', hobbies: ['fishing', 'biking', 'karate'] },
{ name: 'Ben', hobbies: ['surfing'] },
];
function transform(people) {
// get all hobbies and remove duplicates
const hobbies = [... new Set(
people.reduce((hobbies, person) => hobbies.concat(person.hobbies), [])
)];
const res = {};
// take a hobby and use it as key
for (let hobby of hobbies) {
res[hobby] = people
.filter((person) => person.hobbies.includes(hobby))
.map((person) => { return { name: person.name }; });
}
return res;
}
console.log(transform(people));

Remove Object inside Array inside another Array Javascript

let bigArray = [
{
Name: 'Alice',
children: [
{Name: 'AliceChild1', Country: 'country1'},
{Name: 'AliceChild2', Country: 'country2'}
]
},
{
Name: 'Bob',
children: [
{Name: 'BobChild1', Country: 'country3'},
{Name: 'BobChild2', Country: 'country4'}
]
},
{
Name: 'Sam',
children: [
{Name: 'SamChild1', Country: 'country5'},
{Name: 'SamChild2', Country: 'country6'}
]
},
]
I want to remove an object from array inside another array. Property Name is unique. For an example if BobChild2 is removed bigArray should return as
let bigArray = [
{
Name: 'Alice',
children: [
{Name: 'AliceChild1', Country: 'country1'},
{Name: 'AliceChild2', Country: 'country2'}
]
},
{
Name: 'Bob',
children: [
{Name: 'BobChild1', Country: 'country3'},
]
},
{
Name: 'Sam',
children: [
{Name: 'SamChild1', Country: 'country5'},
{Name: 'SamChild2', Country: 'country6'}
]
},
]
What is the best way to do this in JavaScript ?
Updated:
My answer
function removeChild(bigArray, childName) {
let copyBigArray = []
bigArray.forEach((item) => {
let Obj = {
Name: item.Name,
children: item.children.filter(c => c.Name !== childName)
}
copyBigArray.push(Obj)
})
return copyBigArray
}
Try this way:
let bigArray = [{
Name: 'Alice',
children: [{
Name: 'AliceChild1',
Country: 'country1'
},
{
Name: 'AliceChild2',
Country: 'country2'
}
]
},
{
Name: 'Bob',
children: [{
Name: 'BobChild1',
Country: 'country3'
},
{
Name: 'BobChild2',
Country: 'country4'
}
]
}
]
bigArray.forEach(function(o) {
o.children = o.children.filter(s => s.Name != 'BobChild2');
});
console.log(bigArray);
To support any nested depth you can do something like this:
function searchAndRemove(arr, query) {
for (var i = arr.length; i > 0; i--) {
if (query == arr[i].Name) {
arr.splice(i, 1);
}
}
if (arr.children) {
searchAndRemove(arr.children, query);
}
}
searchAndRemove(bigArray, 'BobChild2');
This will go through your array recursively until it finds all occurrences of BobChild2 and removes them.
Well the structure isn't optimal because it'll require iterating over 2 arrays, but I'd use filter() (documentation) something like this:
function deepFilter(array, name) {
return array.map(arr => {
if (!arr || !arr.children) {
return arr;
}
arr.children = arr.children.filter(c => c.Name !== name);
return arr;
})
}
Filter has to return a Boolean to know if the element should be returned or not.
Map has to return an element.
If you want to remove an element from the first Array once its children are empty, you could replace the map by a filter.
function deepFilter(array, name) {
return array.filter(arr => {
if (!arr || !arr.children || !arr.children.length) {
return false;
}
arr.children = arr.children.filter(c => c.Name !== name);
return arr && arr.children && arr.children.length;
})
}
--
Use them by doing:
const new = deepFilter(bigArray, 'SamChild1')
Here is an example how you could achieve it:
let bigArray = [
{
Name: 'Alice',
children: [
{Name: 'AliceChild1', Country: 'country1'},
{Name: 'AliceChild2', Country: 'country2'}
]
},
{
Name: 'Bob',
children: [
{Name: 'BobChild1', Country: 'country3'},
{Name: 'BobChild2', Country: 'country4'}
]
},
{
Name: 'Sam',
children: [
{Name: 'SamChild1', Country: 'country5'},
{Name: 'SamChild2', Country: 'country6'}
]
},
]
function filterName(name, data) {
return data.reduce((arr, item) => {
if (item.Name != name) {
if (item.children) item.children = filterName(name, item.children)
arr.push(item)
}
return arr
}, [])
}
console.log(filterName("BobChild2", bigArray));
A main loop for the initial values of the array
Another loop for children values
The first parameter is the array itself that wants to be filtered, for example: bigArray
The second parameter is the value for the filter, for example: BobChild2
The third parameter is the key for the filter, for example: Name
let bigArray = [{
Name: 'Alice',
children: [
{ Name: 'AliceChild1', Country: 'country1' },
{ Name: 'AliceChild2', Country: 'country2' }
]
},
{
Name: 'Bob',
children: [
{ Name: 'BobChild1', Country: 'country3' },
{ Name: 'BobChild2', Country: 'country4' }
]
},
{
Name: 'Sam',
children: [
{ Name: 'SamChild1', Country: 'country5' },
{ Name: 'SamChild2', Country: 'country6' }
]
},
];
function filterBigArray(array, value, filter) {
let result = [];
bigArray.forEach(person => {
let childs = [];
person.children.forEach(children => {
if (children[filter] !== value) {
childs.push(children);
}
});
result.push(childs);
});
return result;
}
let res = filterArray(bigArray, 'BobChild2', 'Name');
console.log(res);
You can also filter different keys, for example:
let res = filterBigArray(bigArray, 'country3', 'Country');
console.log(res);

multilevel object traversing and print value based on key

Here is my object. There could be children inside of children and so on.
I Am trying to print only name for all of them.
Here is JSFiddle
var products = [
{
name: 'Allison',
children: [
{
name: 'John',
children: [
{
name: 'Scott',
children: [],
},
],
},
{
name: 'Sarah',
children: [],
},
]
},
{
name: 'Tony',
children: [
{
name: 'Lucy',
children: [],
}
]
}
This is what I have tried so far, how can I Print name of all children regardless of what level they are in the object??
for(var i = 0; i < products.length; i++)
{
console.log(products[i].name);
if(products[i].children.length > 0) {
console.log(products[i].children);
// Print only name of all children.
}
}
You can use recursive technical.
var products = [ { name: 'Allison', children: [{ name: 'John', children: [ { name: 'Scott', children: [],},],},{ name: 'Sarah', children: [],},]},{name: 'Tony',children: [{name: 'Lucy',children: [],}]}];
const printRecursively = (products) => {
for (const k of products)
{
console.log(k.name);
k.children.length > 0 && printRecursively(k.children); // Recurive here.
}
}
printRecursively(products);
Here is the implementation, please check
Also here
var products = [
{
name: 'Allison',
children: [
{
name: 'John',
children: [
{
name: 'Scott',
children: [],
},
],
},
{
name: 'Sarah',
children: [],
},
]
},
{
name: 'Tony',
children: [
{
name: 'Lucy',
children: [],
}
]
}
];
function printChildrenNames(children) {
for(var i = 0; i < children.length; i++) {
console.log(children[i].name);
if(children[i].children.length > 0) {
printChildrenNames(children[i].children);
}
}
}
printChildrenNames(products)
Here is an iterative solution using object-scan. For your use case a simple recursive solution might be the best choice, however this solution is very clean and easily adjustable when requirements change.
// const objectScan = require('object-scan');
const data = [{ name: 'Allison', children: [{ name: 'John', children: [{ name: 'Scott', children: [] }] }, { name: 'Sarah', children: [] }] }, { name: 'Tony', children: [{ name: 'Lucy', children: [] }] }];
console.log(objectScan(['**.name'], { rtn: 'value', reverse: false })(data));
// => [ 'Allison', 'John', 'Scott', 'Sarah', 'Tony', 'Lucy' ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#14.0.0"></script>
Disclaimer: I'm the author of object-scan

How would you change values/add to nested object data inside array of objects using Javascript?

const beers = [
{
id: '100',
name: 'stoneys'
},
{
id: '200',
name: 'budweiser'
},
{
id: '300',
name: 'miller'
},
{
id: '400',
name: 'corona'
}
];
const people = [
{
name: 'steve',
teams: [
{
name: 'pirates',
beers: ['100']
},
{
name: 'penguins',
beers: ['300']
}
]
},
{
name: 'jim',
teams: [
{
name: 'indians',
beers: ['200']
},
{
name: 'blue jackets',
beers: ['100', '400']
}
]
}
];
let newPeople = people.map(fan => {
fan.teams.map(team => {
team.beers.map(beer => beers.filter(brand => brand.id === beer)[0])
});
});
Above is a sample I put together to best demonstrate my question. I am having trouble understanding why nested mapping (.map()) of object arrays is not allowing me to alter the nested data. When I console log results, I am either getting an "[undefined, undefined]' or the unchanged "people" array.
I would like to return the same array as "people" except replace the nested "beers" array (people.teams.beers[]) with corresponding objects from the "beers" array. Example of a successful result below:
{
name: 'steve',
teams: [
{
name: 'pirates',
beers: [
{
id: '100',
name: 'stoneys'
}
]
},
{
name: 'penguins',
beers: [
{
id: '300',
name: 'miller'
}
]
}
]
}
Array.map expects a function which takes single array element as parameter and returns a mapped value. In your case you're not returning any value from mapping functions therefore you're getting undefined twice
const beers = [
{
id: '100',
name: 'stoneys'
},
{
id: '200',
name: 'budweiser'
},
{
id: '300',
name: 'miller'
},
{
id: '400',
name: 'corona'
}
];
const people = [
{
name: 'steve',
teams: [
{
name: 'pirates',
beers: ['100']
},
{
name: 'penguins',
beers: ['300']
}
]
},
{
name: 'jim',
teams: [
{
name: 'indians',
beers: ['200']
},
{
name: 'blue jackets',
beers: ['100', '400']
}
]
}
];
let newPeople = people.map(fan => {
let teams = fan.teams.map(team => {
let beer = team.beers.map(beer => beers.filter(brand => brand.id === beer)[0]);
return { name: team.name, beers: beer }
});
return { name: fan.name, teams: teams }
});
console.log(newPeople);

Javascript using .filter on nested arrays

I am hoping there is a way to use .filter() for a nested array to make things a little easier on the eyes.
Here is a basic example of what I want.
[
{
people: [
{
name: 'sam',
id: 1
}
{
name: 'john',
id: 2
}
]
},
people: [
{
name: 'marry',
id: 1
},
{
name: 'paul',
id: 1
}
]
},
...
]
can I use .filter to get all the people with id of 1?
I can do it by using .filter with a
for(...){
for(...){
...
}
...
}
but I don't really want nested for loops as this will add unnecessary complexity.
Edit: I would like output to look like a single array of the nested people object
[{
name: 'sam',
id: 1
},
{
name: 'john',
id: 1
},
{
name: 'john',
id: 1
]
You could use Array#filter and Array#map.
var arr = [{people:[{name:'sam',id:1},{name:'john',id:2}]},{people:[{name:'marry',id:1},{name:'paul',id:1}]}],
res = [].concat(...arr.map(v => v.people.filter(c => c.id == 1)));
console.log(res);
filter can certainly be part of the solution:
const people = [];
for (const obj of data) {
people.push(...obj.people.filter(p => p.id === 1));
}
const data = [
{
people: [
{
name: 'sam',
id: 1
},
{
name: 'john',
id: 2
}
]
},
{
people: [
{
name: 'marry',
id: 1
},
{
name: 'paul',
id: 1
}
]
}
];
const people = [];
for (const obj of data) {
people.push(...obj.people.filter(p => p.id === 1));
}
console.log(people);
.as-console-wrapper {
max-height: 100% !important;
}
(You could use data.forEach instead of the for-of loop if you prefer.)
You could use an ES5 approach with concatinating the filtered arrays.
var array = [{ people: [{ name: 'sam', id: 1 }, { name: 'john', id: 2 }] }, { people: [{ name: 'marry', id: 1 }, { name: 'paul', id: 1 }] }],
result = [].concat.apply([], array.map(function (a) {
return a.people.filter(function (p) {
return p.id === 1;
});
}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories