简体   繁体   中英

How to use Lodash to filter a collection of objects by multiple potential properties?

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:

  • Color is Red OR Orange OR Yellow
  • AND Shape is Circle

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?

PS 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?

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> 

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/lodash@4.17.5/lodash.min.js"></script> 

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.

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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM