3

I'd like to filter an array of objects based on multiple tests. For this example, I want to filter an array of objects if the values for the keys aren't null, and that one value for one key is less than 90. I'm currently doing this with a for loop like so:

let filtered = []
for (let i = 0; i < articles.length; i++) {
    if (articles[i].title !== null && articles[i].title.length <= 90 &&
        articles[i].date !== null &&
        articles[i].image !== null &&
        articles[i].description !== null &&
        articles[i].link !== null) {
        filtered.push(articles[i])
    }
}

But it's pretty clumpy and I know the filter method can achieve something similar. But I'm unsure if it can check multiple keys and their values with the same test whilst checking if a specific value passes an independent test too.

3 Answers 3

6

Try:

articles.filter(article => 
  Object.values(article).every(x => (x !== null))
  && article.title.length <= 90
) 

Let's break this down:

articles.filter(article => ...) 

.filter is a function property of type that accepts a callback argument, which it calls for each item. Essentially, we're passing it a function - not executing it right away, which it can call at its leisure. It's sort of like:

let a = alert;

We're not calling the alert function, we're just saving it to a variable. In the case of .filter, we're using it as a pseudo-variable - an argument. Internally, all .filter is doing is:

Array.prototype.filter(callbackFunc) {
    newArr = [];
    for (i=0;i<this.length;i++){
        if (callbackFunc(this[i]) === false){  // We're calling `callbackFunc` manually, for each item in the loop.
            newArr.push(this[i]);
        }
    }

    return newArr;
}

The next bit to explain is the actual callback function we're using. It's defined with ES6 arrow syntax, but it's the equivalent of:

articles.filter(function(article){
  return Object.values(article).every(x => (x !== null))
  && article.title.length <= 90
}) 

The first line of the callback function, Object.values(article).every(x => (x !== null)), can be broken down to:

let values = Object.values(article); // Get the value of all of the keys in the object

function checkFunction(item){ // Define a helper function
    return (x !== null); // Which returns if an item is _not_ null.
}

let result = values.every(checkFunction); // Run `checkFunction` on every item in the array (see below), and ensure it matches _all_ of them.

Finally, we just need to clarify what every does. It's another example of functional JS, where functions accept callback functions as parameters. The internal code looks like this:

Array.prototype.every(callbackFunc) {
    for (i=0;i<this.length;i++){
        if (callbackFunc(this[i]) === false){  // We're calling `callbackFunc` manually, for each item in the loop.
            return false;
        }
    }
    // In JS, returning a value automatically stops execution of the function.
    // So this line of code is only reached if `return false` is never met.
    return true;
}

And && article.title.length <= 90 should hopefully be self-explanatory: while .every returns a boolean value (true or false), a true will only be returned by the callback function to the filter if the second condition is also met, i.e if the every returns true and article.title.length <= 90

Sign up to request clarification or add additional context in comments.

6 Comments

probably the most extendable and cleanest answer! it's a nice one :)
Why did you use the some method rather than every?
I'm working on an explanation now @fellinlovewithaman, I just wanted to FGIW first.
@fellinlovewithaman oh that's a good catch - based upon your original code, I do think it'd be every, since some will only need at least one truthy value, whereas you want all of them to be truthy
@MatthewLiu true, fixed.
|
2

The filter method does exactly this: it takes a conditional (just like that in your if statement and adds it to the array if the condition is met. Your code almost matches the filter syntax exactly, actually:

let filtered = articles.filter(article =>
  article.title !== null
  article.title.length <= 90 &&
  article.date !== null &&
  article.image !== null &&
  article.description !== null &&
  article.link !== null);

Comments

1

yes filter can do this, it just takes a function and applies it to each item in the array array.filter(x => x.title != null && ... etc) the examples in this section is pretty much what you are doing https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Filtering_invalid_entries_from_JSON

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.