underscore how to filter all objects when there are multiple parameters

▼魔方 西西 提交于 2021-02-10 15:09:41

问题


I have a problem how to correctly filter data when it I pass one or several values ​​from filters on the page. For example I have such object with data:

let data = [
    {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
    {'name': 'test 2', 'address': 'Lisbona', 'class_id': [2, 3], 'country': 'Portugal', 'country_id': 12},
    {'name': 'test 3', 'address': 'New York', 'class_id': [2], 'country': 'USA', 'country_id': 20},
    {'name': 'test 4', 'address': 'Atlanta', 'class_id': [2], 'country': 'USA', 'country_id': 20},
    {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
    {'name': 'test 6', 'address': 'Rio', 'class_id': [1], 'country': 'Brazil', 'country_id': 11},
]

I would like to filter specific data when I pass eg one value so that it looks in class_id eg: [3]

but I would also like to filter all objects that meet more conditions, i.e., filter out when class_id: [3] and country_id: 20

so that the result returns the following result

data = [
    {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
    {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
]

I tried solutions using _.where did not help. I also tried with _.filter and _.contains, it works only if I search after class_id but if I give the second condition in filters, which is country_id, unfortunately then nothing returns


回答1:


This question is a good exercise in composition and abstraction. Let's consider some of the functions that Underscore has to offer and how we can use them. Gradually, we'll compose these into a larger whole until we've met your goal.

_.contains takes a collection (array or object) and a value. If the collection contains the value, it returns true, otherwise false.

_.contains([1, 2, 3], 3) // true

_.partial takes a function and any number of arguments that you may want to "pre-fill" as arguments to that function. It then returns a new function with those arguments pre-filled. You can use _ as a placeholder for arguments that you want to leave open.

// a version of _.contains that has the second argument pre-filled
// with the value 3
var contains3 = _.partial(_.contains, _, 3);

// the next line is equivalent to calling _.contains([1, 2, 3], 3)
contains3([1, 2, 3]) // true

_.property takes a name and returns a function. That function takes an object and returns the value of the property with the name you passed.

var getName = _.property('name');

getName({'name': 'test 1'}) // 'test 1'

_.compose takes any number of functions and returns a new function. The arguments that you pass to this new function are fed to the rightmost function that you passed into _.compose. Then, from right to left, every function is called with the result of the function to its right. Finally, the new function returns the result of the leftmost function.

// a function that takes an object, reads its class_id property,
// and checks whether that contains the value 3
var classIdContains3 = _.compose(contains3, _.property('class_id'));

classIdContains3({'class_id': [1, 2, 3]}) // true

We can turn this into a function that will return such a "property contains value checker" for any combination of property name and value.

function propertyContains(name, value) {
    return _.compose(
        _.partial(_.contains, _, value),
        _.property(name)
    );
}

var classIdContains3 = propertyContains('class_id', 3);
classIdContains3({'class_id': [1, 2, 3]}) // true
classIdContains3({'class_id': [1, 2]}) // false

var nameContainsE = propertyContains('name', 'e');
nameContainsE({'name': 'test 1'}) // true
nameContainsE({'name': 'Zorro'}) // false

_.filter takes a collection and a criterion. It returns an array with all elements of the collection that meet the criterion.

_.filter(data, classIdContains3)
//[
//    {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
//    {'name': 'test 2', 'address': 'Lisbona', 'class_id': [2, 3], 'country': 'Portugal', 'country_id': 12},
//    {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]

Besides passing a function as the filter criterion, functions like _.filter also allow several declarative shorthands. For checking properties against an exact value, this notation works:

_.filter(data, {'country_id': 20})
//[
//    {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
//    {'name': 'test 3', 'address': 'New York', 'class_id': [2], 'country': 'USA', 'country_id': 20},
//    {'name': 'test 4', 'address': 'Atlanta', 'class_id': [2], 'country': 'USA', 'country_id': 20},
//    {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]

To narrow down the results by multiple criteria, simply filter multiple times.

// first filter pass
var hasClassId3 = _.filter(data, classIdContains3);

// second filter pass (filtering result of the above)
_.filter(hasClassId3, {'country_id': 20})
//[
//    {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
//    {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]

_.chain and _.value allow for a shorter notation of the above.

_.chain(data)
    .filter(classIdContains3)
    .filter({'country_id': 20})
    .value()
//[
//    {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
//    {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]

Using _.each, we can turn this into a function that will filter by arbitrary combinations of criteria.

function multiFilter(data, criteria) {
    var result = _.chain(data);
    if (criteria.exact) result = result.filter(criteria.exact);
    _.each(criteria.contains, function(value, name) {
        result = result.filter(propertyContains(name, value));
    });
    return result.value();
}

multiFilter(data, {
    exact: {
        country_id: 20,
    },
    contains: {
        class_id: 3,
    },
})
//[
//    {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
//    {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]

multiFilter(data, {
    exact: {
        country_id: 20,
        address: 'New York',
    },
    contains: {
        name: '3',
    },
})
//[
//    {'name': 'test 3', 'address': 'New York', 'class_id': [2], 'country': 'USA', 'country_id': 20},
//]

multiFilter(data, {
    contains: {
        address: 'a',
    },
})
//[
//    {'name': 'test 2', 'address': 'Lisbona', 'class_id': [2, 3], 'country': 'Portugal', 'country_id': 12},
//    {'name': 'test 4', 'address': 'Atlanta', 'class_id': [2], 'country': 'USA', 'country_id': 20},
//]

For more Underscore, functional programming, and search, you may also refer to this answer.



来源:https://stackoverflow.com/questions/62409101/underscore-how-to-filter-all-objects-when-there-are-multiple-parameters

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!