Need help understanding: Filtering an array of objects in Javascript
leanminmachine

leanminmachine @leanminmachine

About: Today I Learned...

Location:
NYC, NY
Joined:
Jul 14, 2018

Need help understanding: Filtering an array of objects in Javascript

Publish Date: Oct 4 '18
35 16

I am still trying to wrap my head around filtering an array of objects in Javascript; spent a couple of hours figuring this out.

I have this array of objects being returned to me from my backend. Inside each object, there is a key that has an array of objects as its value. AND inside this array, there's another array of objects, with another array... Yeah it gets pretty confusing.

So something like this:

{
menuEntities: Array(1)
0:
categoryEntities: Array(2)

0:
categoryId: 1
categoryName: "Main"
menuItemEntities: Array(1)
0: {customisableItemEntities: Array(0), description: "Full of delicious beef", enabled: true, foodItemEntities: Array(0), imagePath: "", menuItemName: "Burger"}
length: 1
__proto__: Array(0)
__proto__: Object


1: {categoryId: 2, categoryName: "Drinks", menuItemEntities: Array(1)}
length: 2
__proto__: Array(0)
isSelected: true
menuId: 1
menuName: "Menu 1"
__proto__: Object
length: 1
__proto__: Array(0)
}
Enter fullscreen mode Exit fullscreen mode

What I wanted to do was build a filter function to return true if my input text includes a particular menuItemName. For example, type in burger into my input field and all the restaurants that contain burger would show up in my search results.

I came across this post on Stack Overflow that suggests to use some

After a bit of tinkering, I got this:

      this.sortedRestaurants = this.sortedRestaurants.filter(function(
        restaurant
      ) {
        if (_.isEmpty(restaurant.menuEntities) == false) {
          return restaurant.menuEntities[0].categoryEntities.some(category =>
            category.menuItemEntities.some(menuItemEntity =>
              menuItemEntity.menuItemName
                .toLowerCase()
                .includes(val.toLowerCase())
            )
          );
        }
      });
Enter fullscreen mode Exit fullscreen mode

And that works for the current use case!

But I don't understand why when I tried forEach initially, this didn't work:

this.sortedRestaurants = this.sortedRestaurants.filter(function(
        restaurant
      ) {
        if (_.isEmpty(restaurant.menuEntities) == false) {
          return restaurant.menuEntities[0].categoryEntities.forEach(e => {
            e.menuItemEntities.forEach(menuItemEntity => {
              menuItemEntity.menuItemName
                .toLowerCase()
                .includes(val.toLowerCase());
            });
          });


        }
      });
Enter fullscreen mode Exit fullscreen mode

To me, wouldn't includes still return a true or false value for the case of the forEach function..?

How would you guys write this function better too?

Comments 16 total

  • diek
    diekOct 4, 2018

    Hi, a filter must return boolean for every element in the array, you are not doing that. The filter will fill your result with only the elements that returned true. You should check the mdn docs for the Array.prototipe.filter, it comes with examples and very well explained.

  • Dian Fay
    Dian FayOct 4, 2018

    includes returns a boolean value but forEach does not (or rather, it returns undefined). So the result of the includes call is simply thrown away if you use forEach instead of some.

    Two obvious improvements to the working method:

    • iterate restaurant.menuEntities with some as well, if it's possible that the result you're looking for is not in the very first one
    • use reduce instead of filter; if you search, there have been numerous guides posted here

    Additionally, if you're retrieving the restaurant & menu records from a relational database you could short-circuit the whole thing by filtering in your query instead of in application logic.

    • leanminmachine
      leanminmachineOct 4, 2018

      Hahhaa for the menuEntities yeah I'll definitely have to use .some if there's more than one.. Thanks for the reminder & explanation, makes sense that the results of includes is thrown away with forEach

  • Rong Sen Ng
    Rong Sen NgOct 4, 2018

    Don't use forEach. You're expecting the end result to be an array. Use .map, .filter, or .reduce with to return a new array.

  • Guillaume Martigny
    Guillaume MartignyOct 4, 2018

    Just to summarize:

    • forEach if you need to act on every item (display all prices)
      • map same, but return a new array with the new values (add 10% tax to all prices)

    • filter if you need to remove items (get prices under 10$)

    • includes if you need to know if the array contains at least one (does one prices is exactly equal to 10$)
      • some same, but you can use a function as assertion (does one price is under 10$)

    • reduce if you need to transform an array into one value (sum all prices)

    ps: Try to never modify a variable (except iterator).

    this.filteredRestaurants = this.sortedRestaurants.filter( // ...
    
    • Dian Fay
      Dian FayOct 4, 2018

      The reduce accumulator doesn't have to be one value -- it's quite useful in places you'd use both filter and map to choose some elements of an array and generate derived values, and you only have to traverse the array once.

      • Guillaume Martigny
        Guillaume MartignyOct 4, 2018

        For large data set I would agree (even tho I never thought of it). But for sub 100 items, the lose in clarity isn't much worth it.

  • Jason Matthews
    Jason MatthewsOct 4, 2018

    Looks like the answer to why your initial solution didn't work has been answered. I just wanted to leave a few suggestions.

    • Avoid using == in favor of ===. == Will attempt to type convert and unless that's what you need you can run into some false positives. If it is what you explicitly need you're better off converting and then comparing with === yourself

    • Instead of checking _.isEmpty(thing) == false, you can write !isEmpty(thing). It'll be easier to read when/if you or anyone else has to come back and do additional work on this code.

    • You're mixing two flavors of syntax with an ES5 function definition and an ES6 fat arrow function definition. It's not wrong but unless you have a good reason for it, it's probably better to stick to one again for code legibility down the line.

  • Nick Taylor
    Nick TaylorOct 4, 2018

    @sarah_edo wrote a cool tool, Array Explorer, that I think you'd find helpful.

    codepen.io/sdras/details/gogVRX

    • leanminmachine
      leanminmachineOct 4, 2018

      This is awesome & definitely useful! Thanks for the intro Nick and ofc thanks Sarah for making this!

  • Jay Asbury
    Jay AsburyOct 4, 2018

    github.com/mihaifm/linq and jquery grep are helpful.

  • Kelly Boland
    Kelly BolandOct 4, 2018

    You may also want to take a look at underscorejs.org, it's a lightweight library that makes things like this much easier and cleaner

    • leanminmachine
      leanminmachineOct 4, 2018

      aye i already am using underscorejs, installed lodash in my project. what would you suggest to get this done though?

      • Jochem Stoel
        Jochem StoelOct 5, 2018

        I think you have gotten enough suggestions by now to 'wrap your head around it'.

  • Adam Crockett 🌀
    Adam Crockett 🌀Oct 6, 2018

    Don't forget filtter and find. Find gives just one result and filter gives many reults.
    If I'm looping through a crowd of people to find on person.
    If I'm looking for a particular group to filter through.

Add comment