DEV Community

leanminmachine
leanminmachine

Posted on

Need help understanding: Filtering an array of objects in Javascript

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?

Top comments (16)

Collapse
 
dmfay profile image
Dian Fay

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.

Collapse
 
leanminmachine profile image
leanminmachine

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

Collapse
 
gmartigny profile image
Guillaume Martigny

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( // ...
Collapse
 
dmfay profile image
Dian Fay

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.

Collapse
 
gmartigny profile image
Guillaume Martigny

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.

Collapse
 
jmatty1983 profile image
Jason Matthews

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.

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

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.

Collapse
 
diek profile image
diek

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.

Collapse
 
nerdy3000 profile image
Kelly Boland

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

Collapse
 
leanminmachine profile image
leanminmachine

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

Collapse
 
jochemstoel profile image
Jochem Stoel

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

Collapse
 
nickytonline profile image
Nick Taylor • Edited

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

codepen.io/sdras/details/gogVRX

Collapse
 
leanminmachine profile image
leanminmachine

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

Collapse
 
vbjay profile image
Jay Asbury

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

Collapse
 
motss profile image
Rong Sen Ng

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.

Collapse
 
krofdrakula profile image
Klemen Slavič