73

Given I have an array of "purpose" objects:

//array of purpose objects:
var purposeObjects = [
    {purpose: "daily"},
    {purpose: "weekly"},
    {purpose: "monthly"}
];

(for simplicity i am omitting other attributes)

Now I want to have a method that returns a specific one of the objects if a matching purpose name is found.

This is not working:

function findPurpose(purposeName){
    return $.grep(purposeObjects, function(){
      return this.purpose == purposeName;
    });
};

findPurpose("daily");

but it actually returns an empty array:

[]

I am using JQuery 1.5.2. I have also tried with $.each() but with no luck. Apparently, most JQuery methods are designed for usage with DOM elements (such as filter().

Any ideas on how to achieve this?

Jesper Rønn-Jensen
  • 106,591
  • 44
  • 118
  • 155

11 Answers11

95

No need for jQuery.

JavaScript arrays have a find method, so you can achieve that in one line:

array.find((o) => { return o[propertyName] === propertyValue })

Example


const purposeObjects = [
    {purpose: "daily"},
    {purpose: "weekly"},
    {purpose: "monthly"}
];

purposeObjects.find((o) => { return o["purpose"] === "weekly" })      

// output -> {purpose: "weekly"}

If you need IE compatibility, import this polyfill in your code.

Tiago_nes
  • 843
  • 7
  • 30
Luca Fagioli
  • 12,722
  • 5
  • 59
  • 57
40

you should pass reference on item in grep function:

function findPurpose(purposeName){
    return $.grep(purposeObjects, function(item){
      return item.purpose == purposeName;
    });
};

Example

Andrei
  • 4,237
  • 3
  • 25
  • 31
  • exactly! That was just what I ended up doing. – Jesper Rønn-Jensen Apr 07 '11 at 10:45
  • 1
    The $.grep function will loop through all the elements of the array, even if the searched object has been already found during the loop. It is not optimal. Check out my answer. – Luca Fagioli Apr 02 '13 at 15:22
  • 2
    Totally agreeing with Luca: $.grep() loops thru the whole object, even if you return in the conditional! In smaller objects, this might be unimportant, but what if the object is really big, like a Calendar till 2034? I'd opt for Luca Fagioli's solution to be the best answer – toesslab Aug 04 '13 at 11:41
33

I personally use a more generic function that works for any property of any array:

function lookup(array, prop, value) {
    for (var i = 0, len = array.length; i < len; i++)
        if (array[i] && array[i][prop] === value) return array[i];
}

You just call it like this:

lookup(purposeObjects, "purpose", "daily");
Johann
  • 12,158
  • 11
  • 62
  • 89
  • Some set of circumstances prevents this from working 100% of the time. I have a handful of methods that rely on this method - called by some method, it always works, other methods it never works. I'm logging all calls and can not see a visible difference from 'works' to 'doesn't. This output show the third iteration should satisfy the test and return 3.true array[i][prop]: 19741 val: 19741 – justSteve Jul 11 '17 at 07:39
29

The error was that you cannot use this in the grep, but you must use a reference to the element. This works:

function findPurpose(purposeName){
    return $.grep(purposeObjects, function(n, i){
      return n.purpose == purposeName;
    });
};

findPurpose("daily");

returns:

[Object { purpose="daily"}]
Jesper Rønn-Jensen
  • 106,591
  • 44
  • 118
  • 155
  • 1
    The $.grep function will loop through all the elements of the array, even if the searched object has been already found during the loop. It is not optimal. Check out my answer. – Luca Fagioli Apr 02 '13 at 15:21
  • 2
    Totally agreeing with Luca: $.grep() loops thru the whole object, even if you return in the conditional! In smaller objects, this might be unimportant, but what if the object is really big, like a Calendar till 2034? I'd opt for Luca Fagioli's solution to be the best answer – toesslab Aug 04 '13 at 11:40
7

Use the Underscore.js findWhere function (http://underscorejs.org/#findWhere):

var purposeObjects = [
    {purpose: "daily"},
    {purpose: "weekly"},
    {purpose: "monthly"}
];

var daily = _.findWhere(purposeObjects, {purpose: 'daily'});

daily would equal:

{"purpose":"daily"}

Here's a fiddle: http://jsfiddle.net/spencerw/oqbgc21x/

To return more than one (if you had more in your array) you could use _.where(...)

jbobbins
  • 1,221
  • 3
  • 15
  • 28
5

Best, Fastest way is

function arrayLookup(array, prop, val) {
    for (var i = 0, len = array.length; i < len; i++) {
        if (array[i].hasOwnProperty(prop) && array[i][prop] === val) {
            return array[i];
        }
    }
    return null;
}
Kalim
  • 347
  • 1
  • 7
  • 17
5

If your array is actually a set of JQuery objects, what about simply using the .filter() method ?

purposeObjects.filter('[purpose="daily"]')
Philip Murphy
  • 870
  • 3
  • 10
  • 24
Douglas
  • 5,229
  • 3
  • 43
  • 54
1

One more solution:

function firstOrNull(array, expr) {
  for (var i = 0; i < array.length; i++) {
    if (expr(array[i]))
      return array[i];
    }
  return null;
}

Using: firstOrNull([{ a: 1, b: 2 }, { a: 3, b: 3 }], function(item) { return item.a === 3; });

This function don't executes for each element from the array (it's valuable for large arrays)

feeeper
  • 2,865
  • 4
  • 28
  • 42
0

I have created a util service for my angular application. It have two function which use very often.

For example you have object.

First getting value from object recursively without throwing undefined error.

{prop: { nestedProp1: {nestedProp2: somevalue}}}; get nestedProp2 2 without undefined checks.

Second filter array on basis

[{prop: { nestedProp1: {nestedProp2: somevalue1}}}, {prop: { nestedProp1: {nestedProp2: somevalue2}}}];

Find object from array with nestedProp2=somevalue2

app.service('UtilService', function(httpService) {
this.mapStringKeyVal = function(map, field) {
    var lastIdentifiedVal = null;
    var parentVal = map;
    field.split('.').forEach(function(val){
        if(parentVal[val]){
            lastIdentifiedVal = parentVal[val]; 
            parentVal = parentVal[val]; 
        }
    });
    return lastIdentifiedVal;
}


this.arrayPropFilter = function(array, field,value) {
    var lastIdentifiedVal = null;
    var mapStringKeyVal = this.mapStringKeyVal;
    array.forEach(function(arrayItem){
        var valueFound = mapStringKeyVal(arrayItem,field);
        if(!lastIdentifiedVal  && valueFound && valueFound==value){
            lastIdentifiedVal = arrayItem;
        }
    });
    return lastIdentifiedVal;
}});

For solution for current question. inject UtilService and call,

UtilService.arrayPropFilter(purposeArray,'purpose','daily');

Or more advanced

UtilService.arrayPropFilter(purposeArray,'purpose.nestedProp1.nestedProp2','daily');
sanjay patel
  • 449
  • 8
  • 17
0

Javascript has a function just for that: Array.prototype.find. As example

function isBigEnough(element) {
  return element >= 15;
}

[12, 5, 8, 130, 44].find(isBigEnough); // 130

It not difficult to extends the callback to a function. However this is not compatible with IE (and partially with Edge). For a full list look at the Browser Compatibility

user2688838
  • 761
  • 5
  • 11
0

copied from polyfill Array.prototype.find code of Array.find, and added the array as first parameter.

you can pass the search term as predicate function

// Example
var listOfObjects = [{key: "1", value: "one"}, {key: "2", value: "two"}]
var result = findInArray(listOfObjects, function(element) {
  return element.key == "1";
});
console.log(result);

// the function you want
function findInArray(listOfObjects, predicate) {
      if (listOfObjects == null) {
        throw new TypeError('listOfObjects is null or not defined');
      }

      var o = Object(listOfObjects);

      var len = o.length >>> 0;

      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }

      var thisArg = arguments[1];

      var k = 0;

      while (k < len) {
        var kValue = o[k];
        if (predicate.call(thisArg, kValue, k, o)) {
          return kValue;
        }
        k++;
      }

      return undefined;
}
Abou-Emish
  • 2,201
  • 1
  • 22
  • 22