Javascript Equivalent to C# LINQ Select

Chris Nevill picture Chris Nevill · Sep 21, 2013 · Viewed 109k times · Source

Following this question here :

Using the checked binding in knockout with a list of checkboxes checks all the checkboxes

I've created some checkboxes using knockout that allow selection from an array. working fiddle taken from above post:

http://jsfiddle.net/NsCXJ/

Is there a simple way of creating an array of just the fruit's ID's?

I'm more at home with C# where I would do something along the lines of selectedFruits.select(fruit=>fruit.id);

Is there some method/ready made function for doing something similar with javascript/jquery? Or would the simplest option be to loop through the list and create a second array? I intend to post the array back to the server in JSON so am trying to minimize the data sent.

Answer

Johan picture Johan · Sep 21, 2013

Yes, Array.map() or $.map() does the same thing.

//array.map:
var ids = this.fruits.map(function(v){
    return v.Id;
});

//jQuery.map:
var ids2 = $.map(this.fruits, function (v){
    return v.Id;
});

console.log(ids, ids2);

http://jsfiddle.net/NsCXJ/1/

Since array.map isn't supported in older browsers, I suggest that you stick with the jQuery method.

If you prefer the other one for some reason you could always add a polyfill for old browser support.

You can always add custom methods to the array prototype as well:

Array.prototype.select = function(expr){
    var arr = this;
    //do custom stuff
    return arr.map(expr); //or $.map(expr);
};

var ids = this.fruits.select(function(v){
    return v.Id;
});

An extended version that uses the function constructor if you pass a string. Something to play around with perhaps:

Array.prototype.select = function(expr){
    var arr = this;

    switch(typeof expr){

        case 'function':
            return $.map(arr, expr);
            break;

        case 'string':

            try{

                var func = new Function(expr.split('.')[0], 
                                       'return ' + expr + ';');
                return $.map(arr, func);

            }catch(e){

                return null;
            }

            break;

        default:
            throw new ReferenceError('expr not defined or not supported');
            break;
    }

};

console.log(fruits.select('x.Id'));

http://jsfiddle.net/aL85j/

Update:

Since this has become such a popular answer, I'm adding similar my where() + firstOrDefault(). These could also be used with the string based function constructor approach (which is the fastest), but here is another approach using an object literal as filter:

Array.prototype.where = function (filter) {

    var collection = this;

    switch(typeof filter) { 

        case 'function': 
            return $.grep(collection, filter); 

        case 'object':
            for(var property in filter) {
              if(!filter.hasOwnProperty(property)) 
                  continue; // ignore inherited properties

              collection = $.grep(collection, function (item) {
                  return item[property] === filter[property];
              });
            }
            return collection.slice(0); // copy the array 
                                      // (in case of empty object filter)

        default: 
            throw new TypeError('func must be either a' +
                'function or an object of properties and values to filter by'); 
    }
};


Array.prototype.firstOrDefault = function(func){
    return this.where(func)[0] || null;
};

Usage:

var persons = [{ name: 'foo', age: 1 }, { name: 'bar', age: 2 }];

// returns an array with one element:
var result1 = persons.where({ age: 1, name: 'foo' });

// returns the first matching item in the array, or null if no match
var result2 = persons.firstOrDefault({ age: 1, name: 'foo' }); 

Here is a jsperf test to compare the function constructor vs object literal speed. If you decide to use the former, keep in mind to quote strings correctly.

My personal preference is to use the object literal based solutions when filtering 1-2 properties, and pass a callback function for more complex filtering.

I'll end this with 2 general tips when adding methods to native object prototypes:

  1. Check for occurrence of existing methods before overwriting e.g.:

    if(!Array.prototype.where) { Array.prototype.where = ...

  2. If you don't need to support IE8 and below, define the methods using Object.defineProperty to make them non-enumerable. If someone used for..in on an array (which is wrong in the first place) they will iterate enumerable properties as well. Just a heads up.