Easily stripping “blank values” from an array
By Delicious Insights • Published on May 11, 2020

This post is also available in French.

Welcome to the eighth article of our daily series “19 nuggets of vanilla JS.”. Need to clean up an array? We’ve got a lot of solutions, and some are… super concise!

The series of 19

Check out surrounding posts from the series:

  1. Short-circuiting nested loops
  2. Inverting two values with destructuring
  3. Easily stripping “blank values” from an array
  4. Long live numeric separators!
  5. Properly sorting texts
  6. …and beyond! (fear not, all 19 are scheduled already)…

A few reminders on filter

Since ES5 (2009), the Array type has sported many iterative methods directly inspired by Prototype.js’ Enumerable module:

  • forEach to invoke a callback on each element (you should now favor for…of);
  • map to produce a derived array via a transform function;
  • every and some to determine whether a predicate (a yes/no function) is passed by all or some items of the array;
  • reduce and reduceRight to produce a single consolidated value by traversing all values in the array (e.g. a sum, a concatenation, a hash);
  • Finally, filter to produce a derived array that only retains values that satisfied a predicate.

With the exception of reduce and reduceRight that accept 3, all other methods accept 2 arguments:

  1. The callback function (for filter, that would be the predicate);
  2. The context: if the callback function expects a specific this but passing it by reference would “lose it” (which means it was declared using function or the shorthand method syntax), this optional argument lets us specify the correct context (which is super useful and performant).

(By the way, these callbacks get not one but three arguments: the value, its index, and the entire array. This can come in super handy…)

Here are a few examples:

function isEven(n) {
  return n % 2 === 0

const values = [1, 1, 2, 3, 5, 8, 13, 21, 34]
values.filter(isEven) // => [2, 8, 34]

const numbers = [1, Math.PI, 42, Infinity]
numbers.filter(Number.isInteger) // => [1, 42]

“Blank values”?

“Stripping blank values,” meaning what, exactly? What is a “blank” value? Well, this will largely depend on your actual needs.

Most often, you’ll have an array of numbers (where zero is deemed invalid) or strings (where empty strings are deemed invalid). In both cases, false, null, NaN and undefined would be regarded as invalid too.

This is the super-easy case we’ll see later. But what if your situation is different? Perhaps you want zeroes? Or false? Or you want to remove whitespace-only strings?

No worries, I’ve got a series of cute handy predicates for you.

A bunch of useful predicates

Value(s) to test for Predicate
undefined x === undefined or typeof x === 'undefined'
null or undefined x == null (note the loose equality)
empty string ('') Use as boolean, or x === ''
whitespace-only string x.trim() as boolean, or x.trim() === ''
Text convertible to number (except NaN) Number
“Usable” number (neither NaN nor infinite) Number.isFinite (ES2015)
Integer Number.isInteger (ES2015)
Reliable integer1 Number.isSafeInteger (ES2015)

_1 Every Number in JS relies on the IEEE 754 standard; these are floating-point 64-bit numbers with a maximum of 15 digits of precision. Beyond a certain limit, integers are rounded to their closest representation. Try evaluating 9999999999999999 in your console or Node REPL, and then even 1234567890123456789…_

If you’re processing texts, but want to consider falsy values (e.g. null, undefined, false or NaN) as empty strings, you can go with String(x || '') (because String(undefined) would result in 'undefined', among other things).

If you regard whitespace-only strings as empty too: String(x || '').trim() is your friend. Examples:

const data = [
  '   ',

// Only _truthy_ values, except whitespace-only strings:
data.filter((x) => String(x || '').trim())
// => [1, 42, 1e+100, Infinity, '3.14', '42', 'Infinity']

// Only reliable integers
// => [0, 1, 42]

// Only values convertible to “reliable” integers:
data.filter((x) => Number.isSafeInteger(Number(x)))
// => [0, 1, 42, false, '', '   ', '42'] (false turns into 0)

Need to reject instead of retaining?

Sometimes you’ve got a predicate available, that might contain some fairly complex code, and tough luck: it tests the exact opposite of what you want. It says “yes” when you want to reject and “no” when you want to retain. Alas, you only have filter around, there is no reject method.

Please don’t go rewriting that predicate yourself trying to invert its logic (if the predicate is complex, this can quickly break down). The easiest way is to use a negator:

function isEven(n) {
  // Pretend this code is actually complex…
  return n % 2 === 0

const values = [1, 1, 2, 3, 5, 8, 13, 21, 34]
const odds = values.filter((n) => !isEven(n))
// => [1, 1, 3, 5, 13, 21]

If you do this a lot, you can make the negation generic:

function not(fn) {
  return (x) => !fn(x)

// Or for any signature:
function not(fn) {
  return (...args) => !fn(...args)

const odds = values.filter(not(isEven))

As you might expect, Lodash provides this: _.negate

The super-easy case: Boolean!

We mentioned earlier the rather widespread case of an array of numbers (where zeroes are invalid) or strings (where empty strings are invalid). In both cases, false, null, NaN and undefined would also be regarded as invalid.

When you want to filter out all falsy values, there’s a super-concise way:

data.filter(Boolean) // 😎

It’s sort of like the data.compact you’d find in other languages (e.g. Ruby): it will strip out null and undefined, which is rather common base case, but also take out false, NaN and '', which is a rather nice bonus. OK, this will also remove 0, so be careful if that’s an issue for you. But I do use this all the time to clean up, for instance, an array where every result item must be a non-empty text or some random object.

Check out our video course: JavaScript: this is it! 🖥

Get in-depth understanding of how this works in JavaScript, from core ground rules to API overrides to arrow functions, binding and much, much more!