331

TypeScript, --strictNullChecks mode.

Suppose I have an array of nullable strings (string | null)[]. What would be a single-expression way to remove all nulls in a such a way that the result has type string[]?

const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = ???;

Array.filter does not work here:

// Type '(string | null)[]' is not assignable to type 'string[]'
array.filter(x => x != null);

Array comprehensions could've work but they are not supported by TypeScript.

Actually the question can be generalized to the problem of filtering an array of any union type by removing entries having one particular type from the union. But let's focus on unions with null and perhaps undefined as these are the most common usecases.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
SergeyS
  • 3,909
  • 2
  • 15
  • 17
  • What's the problem about iterating through the array, check if he index is null and if not adding them to the filtered array? – Markus G. Mar 30 '17 at 13:07
  • 3
    Iteration+if+insertion is what I do now. I find it too wordy. – SergeyS Mar 30 '17 at 13:13
  • In the playground it works great with `array.filter` the way you posted. It doesn't even need the `: string[]`, this is enough: `const filterdArray = array.filter(x => x != null);` and the compiler infers that `filterdArray` is of type `string[]`. What version of typescript are you using? – Nitzan Tomer Mar 30 '17 at 13:38
  • 6
    In the playground select Options and check strictNullChecks – SergeyS Mar 30 '17 at 13:42

22 Answers22

377

You can use a type predicate function in the .filter to avoid opting out of strict type checking:

function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
    return value !== null && value !== undefined;
}

const array: (string | null)[] = ['foo', 'bar', null, 'zoo', null];
const filteredArray: string[] = array.filter(notEmpty);

Alternatively you can use array.reduce<string[]>(...).

2021 update: stricter predicates

While this solution works in most scenarios, you can get a more rigorous type check in the predicate. As presented, the function notEmpty does not actually guarantee that it identifies correctly whether the value is null or undefined at compile time. For example, try shortening its return statement down to return value !== null;, and you'll see no compiler error, even though the function will incorrectly return true on undefined.

One way to mitigate this is to constrain the type first using control flow blocks, and then to use a dummy variable to give the compiler something to check. In the example below, the compiler is able to infer that the value parameter cannot be a null or undefined by the time it gets to the assignment. However, if you remove || value === undefined from the if condition, you will see a compiler error, informing you of the bug in the example above.

function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
  if (value === null || value === undefined) return false;
  const testDummy: TValue = value;
  return true;
}

A word of caution: there exist situations where this method can still fail you. Be sure to be mindful of issues associated with contravariance.

Bijou Trouvaille
  • 8,794
  • 4
  • 39
  • 42
  • 1
    I don't think this is right `value !== null && value !== undefined` isn't going to return a string, it is going to return true or false. Plus, passing a null or undefined in gives you a value that is null or undefined, so it doesn't really constrain. The problem is that this function isn't the thing actually doing the filtering so it can't really make the guarantee. – mfollett Nov 01 '17 at 04:03
  • Does `notEmpty` guarantee that it constrains the type from `string|null` down to `string`? Not really. The assurance that you do get with type predicates, is that if it has the wrong type, you won't be able to use it in a filter, which is still useful because you can easily fill in the gap with just a few unit tests. Unit test the definition, and the usage is covered by the type system. – Bijou Trouvaille Mar 24 '19 at 17:01
  • 1
    @Bijou I don't understand, it does indeed constrain the type down to `TValue` on my end. – starikcetin Feb 18 '21 at 15:48
  • 1
    @S.TarıkÇetin consider the fact that you will get no compiler error if the return value of the notEmpty function is reduced to `return value !== null;`. – Bijou Trouvaille Feb 18 '21 at 20:11
  • @BijouTrouvaille I see, thanks for explaining that. But I think that is a localized issue that is easy to unit tests as you mentioned. – starikcetin Feb 19 '21 at 08:39
  • 3
    2022: ```function notNull(value: T): value is NonNullable { return value != null; }``` – kien_coi_1997 Sep 26 '22 at 05:39
236

Similar to @bijou-trouvaille's answer, you just need to declare the <arg> is <Type> as the output of the filter function:

array.filter((x): x is MyType => x !== null);
alukach
  • 5,921
  • 3
  • 39
  • 40
  • 34
    Attractive. But this is not typesafe. It is just as bad as using an "as". Typescript won't complain if you wrote this: `const realArr: number[] = arr.filter((x): x is number => x === undefined);` which actually returns an array of undefines. – V Maharajh Apr 14 '20 at 20:17
  • 2
    @VivekMaharajh This is a great point, thanks for pointing it out. – alukach Apr 15 '20 at 03:51
  • 24
    @VivekMaharajh [user-defined type guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) are never "type-safe" in the way you seem to expect: `const isString = (x: number | string): x is string => true;` is perfectly fine, even though it'll report `true` for numbers. If you mess up your type guards, your type system will be buggy. That's equally true for this answer as it is for the accepted one. Do you actually have a "type-*safer*" way to achieve the question at hand? – panepeter Oct 07 '20 at 14:17
  • 4
    I expected that many folks would read this without realizing that it included an unchecked type assertion. These folks may end up copy/pasting it instead of writing the more verbose alternative that doesn't require any type assertions ```` const removeNulls = (arr: (string | null)[]): string[] => { const output: string[] = []; for (const value of arr) { if (value !== null) { output.push(value); } } return output; }; ```` – V Maharajh Oct 08 '20 at 05:18
230

One more for good measure as people often forget about flatMap which can handle filter and map in one go (this also doesn't require any casting to string[]):

// (string | null)[]
const arr = ["a", null, "b", "c"];
// string[]
const stringsOnly = arr.flatMap(f => f ? [f] : []);
Klesun
  • 12,280
  • 5
  • 59
  • 52
Mike Sukmanowsky
  • 3,481
  • 3
  • 24
  • 31
  • 18
    This should be the top answer. In fact, I'd even change it to `f => !!f ? [f] : []` to simplify. – codeperson Feb 27 '20 at 17:09
  • 7
    Worth noting that [flatMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap) is defined in ECMA-262 (aka ECMAScript 2021). For some it might be an obstacle. – Alex Klaus Jan 04 '21 at 00:55
  • 3
    @AlexKlaus, this is a TypeScript question, what does ECMAScript 2021 have to do with it? You can transpile TypeScript to many targets. – Mike Sukmanowsky Jan 06 '21 at 01:17
  • 8
    It may affect the _compilerOptions_ of [tsconfig.json](https://www.typescriptlang.org/tsconfig) in particular the "_lib_" section ([example](https://stackoverflow.com/a/53559473/968003)) – Alex Klaus Jan 06 '21 at 06:47
  • 1
    This still may complicate code unlike standard `reduce` – Ilya Kushlianski May 12 '21 at 12:04
  • This is very elegant when you want to safe characters while writing your code. But in the end you want to write something that is clearly understandable by your colleagues. So I would suggest to not use this solution when you want to have a clean campground and instead go with `array.filter()`. – Andi May 27 '21 at 13:28
  • 10
    Why does `Array.filter()` not do this? Is there just not great TypeScript support for `Array.filter()`? – WebSpence Jul 21 '21 at 20:17
  • I'll add that `flatMap` should be very familiar to programmers coming to TypeScript from Swift, which commonly uses `compactMap` for this purpose. https://developer.apple.com/documentation/swift/sequence/2950916-compactmap – blwinters Nov 03 '21 at 14:08
  • 6
    I believe this will work as well `arr.flatMap(f => f || [])` – Sarsaparilla Nov 16 '21 at 16:29
  • 5
    Great suggestion @Sarsaparilla, though I think `??` would be better because `||` will filter out all falsy values, like `0` and `""` – blwinters Jan 18 '22 at 17:04
  • 3
    What would the performance be like though? As "elegant" as it may look, at first glance to me this looks like it would perform a bunch of unnecessary operations. – 404 Sep 07 '22 at 11:30
  • 1
    This allocates a new array for each element in `arr`, which is a good reason not to use IMO. – Robin Clowers Oct 20 '22 at 21:13
  • `flatMap` looks awesome for filtering out types. Is there a reason why filter can't do this, though? – Ehtesh Choudhury Feb 15 '23 at 10:20
34

One liner:

const filteredArray: string[] = array.filter((s): s is string => Boolean(s));

TypeScript playground

The trick is to pass a type predicate (:s is string syntax).

This answer shows that Array.filter requires users to provide a type predicate.

DLight
  • 1,535
  • 16
  • 20
  • 7
    `!!s` (bang-bang) can be used instead of `Boolean(s)` – Alex Po Sep 14 '21 at 08:45
  • 9
    @AlexPo it's a lot less clear so i'd advise against it – somebody Dec 13 '21 at 08:54
  • 2
    I really like this answer, I've never thought about using type guards in an anonymous function. It also avoids to create many arrays, [like in the answer by Mike Sukmanowsky](https://stackoverflow.com/a/59726888/1513045). – Nesk Feb 13 '22 at 10:40
25

Just realized that you can do this:

const nonNull = array.filter((e): e is Exclude<typeof e, null> => e !== null)

So that you:

  1. get a one-liner, no additional functions
  2. do not have to know the type of array elements, so you can copy this everywhere!
std4453
  • 528
  • 7
  • 12
  • 4
    This one should be the accepted idea, as it's using [utility types](https://www.typescriptlang.org/docs/handbook/utility-types.html) to provide type narrowing information to TypeScript – fant0me Sep 08 '22 at 08:08
  • Agreed - this is the type safe solution that I was looking for. Thanks! – Rich R Jan 18 '23 at 06:17
13

You can cast your filter result into the type you want:

const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray = array.filter(x => x != null) as string[];

This works for the more general use case that you mentioned, for example:

const array2: (string | number)[] = ["str1", 1, "str2", 2];
const onlyStrings = array2.filter(x => typeof x === "string") as string[];
const onlyNumbers = array2.filter(x => typeof x === "number") as number[];

(code in playground)

Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
12

Here is a solution that uses NonNullable. I find it even a little bit more concise than the accepted answer by @bijou-trouvaille

function notEmpty<TValue>(value: TValue): value is NonNullable<TValue> {
    return value !== null && value !== undefined;
}
const array: (string | null | undefined)[] = ['foo', 'bar', null, 'zoo', undefined];

const filteredArray: string[] = array.filter(notEmpty);
console.log(filteredArray)
[LOG]: ["foo", "bar", "zoo"]
Mathias Dpunkt
  • 11,594
  • 4
  • 45
  • 70
  • This will not just remove nulls or undefined from the array. Instead it will remove any falsy value such as 0 an empty string or just the boolean false. – Serge Bekenkamp Jan 14 '22 at 11:00
  • @SergeBekenkamp sorry, my explanation was a bit unclear. I just wanted to point out that the usage of `NonNullable` makes sense here. I adapted my answer accordingly. – Mathias Dpunkt Jan 28 '22 at 10:24
11

To avoid everybody having to write the same type guard helper functions over and over again I bundled functions called isPresent, isDefined and isFilled into a helper library: https://www.npmjs.com/package/ts-is-present

The type definitions are currently:

export declare function isPresent<T>(t: T | undefined | null): t is T;
export declare function isDefined<T>(t: T | undefined): t is T;
export declare function isFilled<T>(t: T | null): t is T;

You can use this like so:

import { isDefined } from 'ts-is-present';

type TestData = {
  data: string;
};

const results: Array<TestData | undefined> = [
  { data: 'hello' },
  undefined,
  { data: 'world' }
];

const definedResults: Array<TestData> = results.filter(isDefined);

console.log(definedResults);

When Typescript bundles this functionality in I'll remove the package. But, for now, enjoy.

Robert Massaioli
  • 13,379
  • 7
  • 57
  • 73
7

If you already use Lodash, you can use compact. Or, if you prefer Ramda, the ramda-adjunct has also compact function.

Both have types, so your tsc will be happy and get the correct types as a result.

From Lodash d.ts file:

/**
 * Creates an array with all falsey values removed. The values false, null, 0, "", undefined, and NaN are
 * falsey.
 *
 * @param array The array to compact.
 * @return Returns the new array of filtered values.
 */
compact<T>(array: List<T | null | undefined | false | "" | 0> | null | undefined): T[];
David
  • 2,528
  • 1
  • 23
  • 29
6

You can use the is construct to narrow the type down:

enter image description here

enter image description here

enter image description here

enter image description here

Ivan Sanz Carasa
  • 1,141
  • 8
  • 16
3

simply use

array.filter(Boolean);

This will work for all truth values.

This, unfortunately, do not provide type inference, found this solution on here


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

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

const arr =["hello","felow","developer","",null,undefined];

const truthyArr = arr.filter(truthy);

// the type of truthyArr will be string[]

  • 1
    It does not asset the type in TS (for some unfortunate reason) – Dmitri Pisarev Jan 11 '21 at 08:03
  • 1
    @DmitriPisarev if you want type infer in, you can use something like ``` type Truthy = T extends false | '' | 0 | null | undefined ? never : T; function truthy(value: T): value is Truthy { return Boolean(value); } const truthyArr = arr.filter(truthy); ``` – shrijan tripathi Jan 12 '21 at 06:40
2

I think this will be an easy approach, with more cleaner code

const array: (string | null)[] = ['foo', 'bar', null, 'zoo', null];
const filteredArray: string[] = array.filter(a => !!a);
  • 1
    This solution is not type safe - it doesn't compile with `strictNullChecks` turned on. – Jan Aagaard Apr 29 '20 at 10:45
  • 1
    Also beware that an [empty string `''` is considered falsy](https://medium.com/better-programming/javascript-bang-bang-i-shot-you-down-use-of-double-bangs-in-javascript-7c9d94446054) and thus removed during the filtering – Roman Vottner Jun 16 '20 at 11:45
1

I believe you have it all good except that the type checking just makes the filtered type not be different than the return type.

const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = array.filter(f => f !== undefined && f !== null) as any;
console.log(filterdArray);
Digvijay
  • 27
  • 2
  • You're right, temporary types optout will work. Is stricter solution possible? – SergeyS Mar 30 '17 at 13:45
  • That was my first instinct - but typescript won't allow it. However, since filteredArray is typed as string[] it is as strict as it can get IMO. – Digvijay Mar 30 '17 at 13:58
1

I've come back to this question many times hoping some new Typescript feature or typing may fix it.

Here's a simple trick I quite like for when combining map with a subsequent filter.

const animals = ['cat', 'dog', 'mouse', 'sheep'];

const notDogAnimals = animals.map(a => 
{
   if (a == 'dog')
   {
      return null!;   // just skip dog
   }
   else {
      return { animal: a };
   }
}).filter(a => a);

You'll see I'm returning null! which actually becomes type never - meaning that the final type doesn't have null.

This is a slight variation on the original question but I find myself in this scenario quite often and it helps avoid another method call. Hopefully someday Typescript will come up with a better way.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
1

If you can accept the overhead of another .map() an elegant solution is using the Non-null assertion operator.

const array = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = array.filter(s => s != null).map(s => s!);

If you'd like to keep the undefines you can use typeof on the variable and the utility type Exclude to remove nulls from the type.

const array = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = array
  .filter(s => s !== null)
  .map(s => s as Exclude<typeof s, null>);
kjetilh
  • 4,821
  • 2
  • 18
  • 24
0

If you are checking null with other conditions using filter simply this can be used hope this helps for some one who is looking solutions for an object array

array.filter(x => x != null);
array.filter(x => (x != null) && (x.name == 'Tom'));
Akitha_MJ
  • 3,882
  • 25
  • 20
0

TypeScript has some utilities to infer the type of the array and exclude the null values from it:

const arrayWithNulls = ["foo", "bar", null, "zoo", null]

type ArrayWithoutNulls = NonNullable<typeof arrayWithNulls[number]>[]

const arrayWithoutNulls = arrayWithNulls.filter(x => x != null) as ArrayWithoutNulls

Longer but safer than just manually casting as string[] on your new array.

Step by step:

  1. Get the types from the original array:
typeof arrayWithNulls[number] // => string | null
  1. Exclude the null values:
NonNullable<typeof arrayWithNulls[number]> // => string
  1. Make it an array:
NonNullable<typeof arrayWithNulls[number]>[] // => string[]

Links:

GG.
  • 21,083
  • 14
  • 84
  • 130
0

Using reduce

Some answers suggest reduce, here is how:

const languages = ["fr", "en", undefined, null, "", "de"]

// the one I prefer:
languages.reduce<string[]>((previous, current) => current ? [...previous, current] : previous, [])

// or
languages.reduce((previous, current) => current ? [...previous, current] : previous, Array<string>())

// or
const reducer = (previous: string[], current: string | undefined | null) => current ? [...previous, current] : previous
languages.reduce(reducer, [])

Result: ["fr", "en", "de"]

TS Playground here.

frouo
  • 5,087
  • 3
  • 26
  • 29
  • That's a work-around, but not a good solution in my opinion: it's "surprising" to see a reduce instead of a filter, and it's less efficient too. – vcarel Jan 31 '22 at 21:40
0
const filterdArray = array.filter(f => !!f) as string[];
Alan Draper
  • 389
  • 2
  • 7
0

The shortest way:

const validData = array.filter(Boolean)
blackgreen
  • 34,072
  • 23
  • 111
  • 129
  • 2
    Nice trick, but it might not be exactly what was requested. If array contains an empty string the filter removes it. This might have unintended consequences. The original request was to remove null values. I'd suggest to edit the response specifying the corner case. – filippo May 26 '22 at 21:17
0

Combining one of my favorite answers above, with some of the generic tricks and an extension to the Array interface, I was able to make a global define that after adding to your module allows for any array to be "squished" removing all null values replacing (any|undefined|null)[] with any[].

Like so: mixedArray.squish() good for chaining and map.

Just add this code somewhere in your module (feel free to leave out the eslint stuff, but my set bugged me about a few things here):

/* eslint-disable no-unused-vars */
/* eslint-disable no-extend-native */
declare global {
  interface Array<T> {
    squish<NonNull, Nullable extends (NonNull | undefined | null)>(): NonNull[];
  }
}

if (!Array.prototype.squish) {
  Array.prototype.squish = function squish<NonNull, T extends(NonNull|undefined|null)>
  (this: T[]): NonNull[] {
    return this.flatMap((e) => (e ? [e] : [])) as NonNull[]
  }
}
BadPirate
  • 25,802
  • 10
  • 92
  • 123
-2

Or you can try the package: @p4ck93/ts-is

https://www.npmjs.com/package/@p4ck493/ts-is

The example uses the CDN method, but the package also supports typescript.

<script>var exports = {};</script>
<script src="//unpkg.com/@p4ck493/ts-is@3.0.1/dist/index.js"></script>
<script>
    const {is} = exports;
    console.log('is.string: ', is.string('')); // true
    console.log('is.string.empty: ', is.string.empty('')); // true
    console.log('is.string.not.empty: ', is.string.not.empty('')); // false
    
    
   const array = ["foo", "bar", null, "zoo", null];
   const filterdArray = array.filter(is.string.not.empty);
   
   console.log('array:', array);
   console.log('filterdArray:', filterdArray);
</script>

UPD

Or TypeScript:

import {is} from '@p4ck493/ts-is';

const array = ["foo", "bar", null, "zoo", null];
const filterdArray = array.filter(is.string.not.empty);

/**
Alternative:

array.filter(is.not.null);
array.filter(is.not.empty);
array.filter(is.string);

**/
Karba
  • 1
  • 1