Reputation: 3518
I have array with following structure:
var topics = [
{
"id": 1,
"name": "topic title 1",
"sub_categories": [
{
"id": 1,
"name": "category title 1",
"indicators": [
{
"id": 1,
"name": "indicator 1",
"sub_category_id": 1
},
{
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}
]
},
{
"id": 6,
"name": "category title 6",
"indicators": [
{
"id": 8,
"name": "indicator 8",
"sub_category_id": 6
}
]
}
]
},
{
"id": 2,
"name": "topic title 2",
"sub_categories": [
{
"id": 2,
"name": "category 2",
"indicators": [
{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}
]
},
{
"id": 4,
"name": "category 4",
"indicators": [
{
"id": 5,
"name": "indicator 5",
"sub_category_id": 4
}
]
}
]
}
];
I need to get filtered array based on value of name property in indicators array, removing non-matched indicators and both topic and sub_categories with empty indicators. So for input of foo
, result would be:
var topics = [
{
"id": 1,
"name": "topic title 1",
"sub_categories": [
{
"id": 1,
"name": "category title 1",
"indicators": [
{
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}
]
}
]
},
{
"id": 2,
"name": "topic title 2",
"sub_categories": [
{
"id": 2,
"name": "category 2",
"indicators": [
{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}
]
}
]
}
];
I tried to use lodash methods based on other similar SO question but all examples either have only one level of nesting or same keys on all levels (ie. children). I would be fine with either getting back new array or mutating existing one.
Upvotes: 2
Views: 4563
Reputation: 386550
You could use an iterative and recursive approach for filtering the given array, without hard wired properties.
const deepFilter = (array, indicator) => {
return array.filter(function iter(o) {
return Object.keys(o).some(k => {
if (typeof o[k] === 'string' && o[k].includes(indicator)) {
return true;
}
if (Array.isArray(o[k])) {
o[k] = o[k].filter(iter);
return o[k].length;
}
});
});
}
const topics = [{ id: 1, name: "topic title 1", sub_categories: [{ id: 1, name: "category title 1", indicators: [{ id: 1, name: "indicator 1", sub_category_id: 1 }, { id: 7, name: "indicator 7 - foo", sub_category_id: 1 }] }, { id: 6, name: "category title 6", indicators: [{ id: 8, name: "indicator 8", sub_category_id: 6 }] }] }, { id: 2, name: "topic title 2", sub_categories: [{ id: 2, name: "category 2", indicators: [{ id: 2, name: "indicator 2 - foo", sub_category_id: 2 }] }, { id: 4, name: "category 4", indicators: [{ id: 5, name: "indicator 5", sub_category_id: 4 }] }] }];
console.log(deepFilter(topics, 'foo'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 7
Reputation: 1463
You can do it using _.filterDeep from deepdash
extension for lodash
:
var endsWith = 'foo';
var foundFoo = _.filterDeep(
obj,
function(value, key) {
return _.endsWith(value.name, endsWith);
},
{ tree: { children: ['sub_categories', 'indicators'] } }
);
Here is a full test for your case
Upvotes: 0
Reputation: 9878
One more implementation.
topics.forEach(function(topic, indexTopic, indexTopicArray) {
topic.sub_categories.forEach(function(subCat, indexsubCat, arraysubCat) {
subCat.indicators = subCat.indicators.filter(indic => indic.name.includes("foo"));
if(subCat.indicators.length === 0) {
indexTopicArray[indexTopic].sub_categories.splice(indexsubCat, 1);
}})});
console.log(topics);
Complete Code.
var topics = [
{
"id": 1,
"name": "topic title 1",
"sub_categories": [
{
"id": 1,
"name": "category title 1",
"indicators": [
{
"id": 1,
"name": "indicator 1",
"sub_category_id": 1
},
{
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}
]
},
{
"id": 6,
"name": "category title 6",
"indicators": [
{
"id": 8,
"name": "indicator 8",
"sub_category_id": 6
}
]
}
]
},
{
"id": 2,
"name": "topic title 2",
"sub_categories": [
{
"id": 2,
"name": "category 2",
"indicators": [
{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}
]
},
{
"id": 4,
"name": "category 4",
"indicators": [
{
"id": 5,
"name": "indicator 5",
"sub_category_id": 4
}
]
}
]
}
];
topics.forEach(function(topic, indexTopic, indexTopicArray) {
topic.sub_categories.forEach(function(subCat, indexsubCat, arraysubCat) {
subCat.indicators = subCat.indicators.filter(indic => indic.name.includes("foo"));
if(subCat.indicators.length === 0) {
indexTopicArray[indexTopic].sub_categories.splice(indexsubCat, 1);
}})});
console.log(topics);
Upvotes: 0
Reputation: 5075
this will also modify existing topics
var result = topics.filter(top =>
(top.sub_categories = top.sub_categories.filter(cat =>
(cat.indicators = cat.indicators.filter(i => i.name.match(/foo/))).length)
).length
);
Example
var topics = [{
"id": 1,
"name": "topic title 1",
"sub_categories": [{
"id": 1,
"name": "category title 1",
"indicators": [{
"id": 1,
"name": "indicator 1",
"sub_category_id": 1
}, {
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}]
}, {
"id": 6,
"name": "category title 6",
"indicators": [{
"id": 8,
"name": "indicator 8",
"sub_category_id": 6
}]
}]
}, {
"id": 2,
"name": "topic title 2",
"sub_categories": [{
"id": 2,
"name": "category 2",
"indicators": [{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}]
}, {
"id": 4,
"name": "category 4",
"indicators": [{
"id": 5,
"name": "indicator 5",
"sub_category_id": 4
}]
}]
}];
var result = topics.filter(top => (top.sub_categories = top.sub_categories.filter(cat => (cat.indicators = cat.indicators.filter(i => i.name.match(/foo/))).length)).length);
console.log(result);
Upvotes: 2
Reputation: 21926
This can pretty much all be done with ES 5 array methods (no library or polyfill needed for IE 9+):
var passed = topics.filter(function(x) {
return x.subcategories.some(function(y) {
return y.indicators.some(function(z) {
return Boolean(z.name.match(/foo/));
});
});
});
While this is total one-off code, the situation is perhaps too complicated for an easily digestible general-purpose solution (although I'd love to see someone prove me wrong).
After taking a closer look at the output you will need to use reduce
instead of filter:
var passed = topics.reduce((acc, x) => {
var hasfoo = x.subcategories.reduce((accum, y) => {
var ls = y.indicators.filter(z => z.name.match(/foo/));
if (ls.length) {
accum.push(Object.assign({}, y, {indicators: ls}));
}
return accum;
}, []);
if (hasfoo.length) {
acc.push(Object.assign({}, x, {subcategories: hasfoo}));
}
return acc;
}, []);
Astute readers will note the recursive pattern here. Abstracting that out is left as an exercise, I'm tapped out. Object.assign
will need to be polyfilled for old browsers (trivial though).
Upvotes: 4
Reputation: 350127
Here is an ES6 solution based on reduce
, filter
and Object.assign
:
function filterTree(topics, find) {
return topics.reduce(function (acc, topic) {
const sub_categories = topic.sub_categories.reduce(function (acc, cat) {
const indicators = cat.indicators.filter( ind => ind.name.includes(find) );
return !indicators.length ? acc
: acc.concat(Object.assign({}, cat, { indicators }));
}, []);
return !sub_categories.length ? acc
: acc.concat(Object.assign({}, topic, { sub_categories }));
}, []);
}
// sample data
const topics = [
{
"id": 1,
"name": "topic title 1",
"sub_categories": [
{
"id": 1,
"name": "category title 1",
"indicators": [
{
"id": 1,
"name": "indicator 1",
"sub_category_id": 1
},
{
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}
]
},
{
"id": 6,
"name": "category title 6",
"indicators": [
{
"id": 8,
"name": "indicator 8",
"sub_category_id": 6
}
]
}
]
},
{
"id": 2,
"name": "topic title 2",
"sub_categories": [
{
"id": 2,
"name": "category 2",
"indicators": [
{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}
]
},
{
"id": 4,
"name": "category 4",
"indicators": [
{
"id": 5,
"name": "indicator 5",
"sub_category_id": 4
}
]
}
]
}
];
// Call the function
var res = filterTree(topics, 'foo');
// Output result
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 7