Reputation: 51
I'm building a simple filtering UI. I want to filter a collection of objects using a logical AND, where the object’s match depends on multiple potential values for each in an arbitrary set of keys. The objects to filter look like:
collection = [
{
'Kind': 'Instrument',
'Color': 'Orange',
'Shape': 'Square',
'Name': 'Foobar',
},
...
]
And the user’s point-and-click filter results look like:
filter = {
'Color': ['Red', 'Orange', 'Yellow'],
'Shape': ['Circle']
}
In this case, I want to filter the collection to all objects that are:
The filter
object has an arbitrary number of keys, so I can’t easily write a manual filter function like this:
results = _.filter(collection, item => {
return _.includes(['Red', 'Orange', 'Yellow'], item['Color']) && _.includes(['Circle'], item['Shape'])
})
What is the cleanest way to achieve this using Lodash? Do I need to loop over each key in filter
for each item in collection
inside of my _.filter
or is there a better way?
P.S. I don’t have the right language to speak about what I’m trying to do. What are the best keywords to describe this question?
Upvotes: 0
Views: 2610
Reputation: 357
This will work for a list of items where the givenProperty you want to filter on is either a string like 'doorColour' or an array of strings representing the path to the givenProperty like ['town', 'street', 'doorColour'] for a value nested on an item as town.street.doorColour.
It also can filter on more than one value so you could you just need pass in an array of substrings representing the string values you want to keep and it will retain items that have a string value which contains any substring in the substrings array.
The final parameter 'includes' ensures you retain these values if you set it to false it will exclude these values and retain the ones that do not have any of the values you specified in the substrings array
import { flatMap, path } from 'lodash/fp';
const filteredListForItemsIncludingSubstringsOnAGivenProperty = (items, givenProperty, substrings, including=true) => flatMap((item) =>
substrings.find((substring) => path(givenProperty)(item) && path(givenProperty)(item).includes(substring))
? including
? [item]
: []
: including
? []
: [item])(items);
Upvotes: 0
Reputation: 6932
Applying a bit of functional style, I'd just do as you're stating. Hopefully the code is explanatory enough, otherwise please let me know and I'll try to clarify things (with lodashfp would be a little clearer though I don't think you're using it).
Hope it helps.
const isValIncluded = item => (value, key) => _.includes(value, _.get(item, key, null));
const isValid = filters => item => _.every(filters, isValIncluded(item));
const filterCol = coll => filters => _.filter(coll, isValid(filters));
const collection = [{
'Kind': 'Instrument',
'Color': 'Orange',
'Shape': 'Square',
'Name': 'Foobar',
},
{
'Kind': 'Instrument',
'Color': 'Orange',
'Shape': 'Circle',
'Name': 'Foobar',
},
{
'Kind': 'XXX',
'Color': 'YYY',
'Shape': 'ZZZ',
'Name': 'FFF',
},
{
'Kind': 'Instrument',
'Color': 'Red',
'Shape': 'Circle',
'Name': 'Foobar',
}
];
const filters = {
'Color': ['Red', 'Orange', 'Yellow'],
'Shape': ['Circle']
};
console.log(filterCol(collection)(filters));
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
Upvotes: 1
Reputation: 3747
You are very close:
const collection = [
// Won't be in the output.
{
'Kind': 'Instrument',
'Color': 'Orange',
'Shape': 'Square',
'Name': 'Foobar',
},
// Will be in the output.
{
'Kind': 'Instrument',
'Color': 'Orange',
'Shape': 'Circle',
'Name': 'Foobar',
},
// Won't be in the output.
{
'Kind': 'Derp',
'Color': 'NoWorky',
'Shape': 'HAHAHAHA',
'Name': 'uWin',
}
];
const filter = {
'Color': ['Red', 'Orange', 'Yellow'],
'Shape': ['Circle']
};
const results = _.filter(collection, (item) => {
return _.chain(filter)
.keys()
.reduce((currentBoolean, next) => {
console.log(currentBoolean, next, filter[next], item[next]);
return _.isNil(item[next]) ? currentBoolean : _.includes(filter[next], item[next]) && currentBoolean;
}, true)
.value();
// This doesn't work because you're trying to do arbitrary keys.
// return _.includes(filter.Color, item.Color) && _.includes(filter.Shape, item.Shape));
});
console.log(results);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
Upvotes: 1
Reputation: 24
You could simplify using
results = _.filter(collection, item => item.Shape==='Circle' && _.includes(['Red', 'Orange', 'Yellow'], item.Color));
Probably a good idea to avoid confusion by naming the filter
object differently here.
Upvotes: 0