Qwertiy
Qwertiy

Reputation: 21510

Typescript and filter Boolean

Consider following code with strictNullChecks turned on:

var a: (number | null)[] = [0, 1, 2, 3, null, 4, 5, 6];
var b: { value: number; }[] = a.map(x => x != null && { value: x }).filter(Boolean);

It fails to compile due to:

Type '(false | { value: number; })[]' is not assignable to type '{ value: number; }[]'.
  Type 'false | { value: number; }' is not assignable to type '{ value: number; }'.
    Type 'false' is not assignable to type '{ value: number; }'.

But it is absolutely clear, that false will be filtered away by .filter(Boolean).

Same problem with null.

Is there a way (except writing as number[]) to mark that value doesn't contain false or null?

Upvotes: 63

Views: 37547

Answers (4)

keyvanm
keyvanm

Reputation: 191

export function isTruthy<T>(value?: T | undefined | null | false): value is T {
  return !!value
}

Then do

const arrayWithNoFalsyValues = arrayWithFalsyValues.filter(isTruthy)

arrayWithNoFalsyValues type will be T[] and won't be (T | null | ...)[]

Fill free to add more falsy values/types to the type of value in isTruthy(value)

Upvotes: 4

Girish Gupta
Girish Gupta

Reputation: 1293

You can exclude any non null values like this

type ValidValue<T> = Exclude<T, null | undefined | 0 | '' | false>;
const BooleanFilter = <T>(x: T): x is ValidValue<T> => Boolean(x);
var a: (number | null)[] = [0, 1, 2, 3, null, 4, 5, 6, undefined];
const values = Object.values(a).filter(BooleanFilter);
console.log(values)  // [ 1, 2, 3, 4, 5, 6 ]

Upvotes: 5

justerest
justerest

Reputation: 964

You can use functions like this

function nonNullable<T>(value: T): value is NonNullable<T> {
  return value !== null && value !== undefined;
}

type Truthy<T> = T extends false | '' | 0 | null | undefined ? never : T; // from lodash

function truthy<T>(value: T): value is Truthy<T> {
    return !!value;
}

[1, 2, 0, null].filter(nonNullable) // number[]
[1, 2, 0, null].filter(truthy) // number[]

NonNullable - https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types

Upvotes: 79

jcalz
jcalz

Reputation: 330161

If you really don't want to change the generated JavaScript, and instead prefer to force the TypeScript compiler to recognize that Boolean is serving as a guard against false values, you can do this:

type ExcludesFalse = <T>(x: T | false) => x is T;     
var b: { value: number; }[] = a
  .map(x => x != null && { value: x })
  .filter(Boolean as any as ExcludesFalse);

This works because you are asserting that Boolean is a type guard, and because Array.filter() is overloaded to return a narrowed array if the callback is a type guard.

The above (Boolean as any as ExcludesFalse) is the cleanest code I could come up with that both works and doesn't change the generated JavaScript. The constant Boolean is declared to be an instance of the global BooleanConstructor interface, and you can merge an ExcludesFalse-like type guard signature into BooleanConstructor, but not in a way that allows you to just say .filter(Boolean) and have it work. You can get fancier with the type guard and try to represent guarding against all falsy values (except NaN) but you don't need that for your example.

Anyway, hope that helps; good luck!

Upvotes: 31

Related Questions