loading...

Need help understanding: Filtering an array of objects in Javascript

leanminmachine profile image leanminmachine ・2 min read

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)
}

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())
            )
          );
        }
      });

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());
            });
          });


        }
      });

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?

Posted on by:

Discussion

markdown guide
 

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.

 

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

 

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( // ...
 

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.

 

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.

 

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.

 

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.

 

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.

 

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

 

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

 

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

 

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

codepen.io/sdras/details/gogVRX

 

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

 
 

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.