64

I would like to filter a collection using array of property value. Given an array of IDs, return objects with matching IDs. Is there any shortcut method using lodash/underscore?

var collections = [{ id: 1, name: 'xyz' },
                   { id: 2,  name: 'ds' },
                   { id: 3,  name: 'rtrt' },
                   { id: 4,  name: 'nhf' },
                   { id: 5,  name: 'qwe' }];
var ids = [1,3,4];

// This works, but any better way?

var filtered = _.select(collections, function(c){    
    return ids.indexOf(c.id) != -1
});
1
  • Not really. But you could deal directly with the filter() method of Arrays prototypes; looks cleaner :) Commented Jun 23, 2013 at 1:34

8 Answers 8

67

If you're going to use this sort of pattern a lot, you could create a mixin like the following, though, it isn't doing anything fundementally different than your original code. It just makes it more developer friendly.

_.mixin({
  'findByValues': function(collection, property, values) {
    return _.filter(collection, function(item) {
      return _.contains(values, item[property]);
    });
  }
});

Then you can use it like this.

var collections = [
    {id: 1, name: 'xyz'}, 
    {id: 2,  name: 'ds'},
    {id: 3,  name: 'rtrt'},
    {id: 4,  name: 'nhf'},
    {id: 5,  name: 'qwe'}
];

var filtered = _.findByValues(collections, "id", [1,3,4]);

Update - This above answer is old and clunky. Please use the answer from Adam Boduch for a much more elegant solution.

_(collections)
  .keyBy('id') // or .indexBy() if using lodash 3.x
  .at(ids)
  .value();
Sign up to request clarification or add additional context in comments.

1 Comment

you're missing the end quote on keyBy. I would correct but SO won't let me add a single character ;)
49

A concise lodash solution that uses indexBy() and at().

// loDash 4
_.chain(collections)
 .keyBy('id')
 .at(ids)
 .value();

// below loDash 4
_(collections)
 .indexBy('id')
 .at(ids)
 .value();

2 Comments

This pithiness of this solution is splendid. And Lodash 4 users can simply replace indexBy with keyBy for this to continue functioning.
If collections doesn't contain any objects that match up with ids, it seems to return an array with one element of undefined. [undefined]. This fails my go test of someArray.length so I added a .filter() _(collections).keyBy('id').at(ids).filter().value();
19

We can also filter like this

var collections = [{ id: 1, name: 'xyz' },
            { id: 2,  name: 'ds' },
            { id: 3,  name: 'rtrt' },
            { id: 4,  name: 'nhf' },
            { id: 5,  name: 'qwe' }];



        var filtered_ids = _.filter(collections, function(p){
            return _.includes([1,3,4], p.id);
        });

        console.log(filtered_ids);

Comments

12

This worked great for me:

let output = _.filter(collections, (v) => _.includes(ids, v.id));

3 Comments

Thanks, this has worked for me too, yet, I have used reject to simplfiy filter let output = _.reject(collections, v => _.includes(ids, v.id));
@edencorbin How can it be achieved if the ids are also a list of objects with both id and name properties
There may be a less code approach but I would reduce that to just the ids, something like const ids = arrayofobjects.map(x=>x.id)
7

I like jessegavin's answer, but I expanded on it using lodash-deep for deep property matching.

var posts = [{ term: { name: 'A', process: '123A' } }, 
             { term: { name: 'B', process: '123B' } }, 
             { term: { name: 'C', process: '123C' } }];

var result = _.filterByValues(posts, 'term.process', ['123A', '123C']);
// results in objects A and C to be returned

jsFiddle

_.mixin({
    'filterByValues': function(collection, key, values) {
        return _.filter(collection, function(o) {
            return _.contains(values, resolveKey(o, key));
        });
    }
});

function resolveKey(obj, key) {
    return (typeof key == 'function') ? key(obj) : _.deepGet(obj, key);
}

If you don't trust lodash-deep or you want support for properties that have dots in their names, here's a more defensive and robust version:

function resolveKey(obj, key) {
    if (obj == null || key == null) {
        return undefined;
    }
    var resolved = undefined;
    if (typeof key == 'function') {
        resolved = key(obj);
    } else if (typeof key == 'string' ) {
        resolved = obj[key];
        if (resolved == null && key.indexOf(".") != -1) {
            resolved = _.deepGet(obj, key);
        }
    }
    return resolved;
}

1 Comment

lodash get is deep by default, so you can do things like _.get(object, 'a[0].b.c');. So all you have to do to make @jessegavin answer support deep properties is change item[property] with _.get(item, property). See lodash documents.
3

These answers didn't work for me, because I wanted to filter on a non-unique value. If you change keyBy to groupBy you can get by.

_(collections)
  .groupBy(attribute)
  .pick(possibleValues)
  .values()
  .flatten()
  .value();

My initial use was to drop things, so I switched out pick with omit.

Thanks Adam Boduch for the starting point.

Comments

1

I noticed many of these answers are outdated or contain auxiliary functions not listed in Lodash documentation. The accepted answer includes deprecated function _.contains and should be updated.

So here is my ES6 answer.

Based on Lodash v4.17.4

_.mixin( {
    filterByValues: ( c, k, v ) => _.filter(
        c, o => _.indexOf( v, o[ k ] ) !== -1
    )
} );

And invoked as such:

_.filterByValues(
    [
        {
            name: 'StackOverflow'
        },
        {
            name: 'ServerFault'
        },
        {
            name: 'AskDifferent'
        }
    ],
    'name',
    [ 'StackOverflow', 'ServerFault' ]
);

// => [ { name: 'StackOverflow' }, { name: 'ServerFault' } ]

Comments

0

Sorry to join the party late, but nowadays the clearest way to do it would be by using _.reject

_.reject(collections, ({id}) => _.includes(ids, id));

Basically, we are rejecting all the values that meet a specific criteria, in this case that the ID is included in the ids array.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.