A way to tell TypeScript to compiler Array.prototype.filter to remove certain types from an array?

I'm trying to filter a null (undefined) element from an array using Array.prototype.filter, but the TypeScript compiler doesn't seem to recognize the derived array of the filter function and hasn't passed the type check.

Assuming the following simplified code, where I have an array with (number | undefined) [] and want the undefined filter to fit into the number [] array.

const arry = [1, 2, 3, 4, "5", 6];
const numArry: number[] = arry
    .map((i) => {
        return typeof i === "number" ? i : void 0;
    })
    .filter((i) => i);

      

The error says:

The type '(number | undefined) []' is not assigned to the type 'number []'. Type 'number | undefined 'is not assigned to type' number '. The type 'undefined' cannot be assigned to the type 'number'.

I can apply the given array to number [] as shown below. The filter function removes undefined.

const arry = [1, 2, 3, 4, "5", 6];
const numArry: number[] = (arry
    .map((i) => {
        return typeof i === "number" ? i : void 0;
    })
    .filter((i) => i) as Number[]);

      

Other than casting, is there a better way to achieve this?

Environment: TSC2.1 with string NullChecks enabled.

+5


source to share


4 answers


Not possible with a built-in function filter

, it has declared to return an array of exactly the same type it receives.

However, you can define your own completely type safe filter function that takes an array and a custom type guard , and returns an array of a different type.

Not sure how useful, but here it is:

function typeFilter<T, R extends T>(a: T[], f: (e: T) => e is R): R[] {
    const r: R[] = [];
    a.forEach(e => { if (f(e)) r.push(e) });
    return r;
}

      



it can be used like this:

const arry = [1, 2, 3, 4, "5", 6];

function isNumber(e): e is number {
    return typeof e === 'number';
}

const numArry: number[] = typeFilter(arry, isNumber);

      

Unfortunately, it isNumber()

must be defined as a separate, explicitly typed function, because the compiler is not smart enough to recognize that an inline function is e => typeof e === 'number'

also a type keeper.

+4


source


Functional signature map(...)

:

map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];

      

In your case, the generic type U

would be:number | undefined

Signature filter(...)

:



filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[];        

      

Since it T

comes from the signature of the array interface ( Array<T>

), the return type will be an array of the same argument type value

(generic type T

). In your case number | undefined

.

This is why your return type is number | undefined

.

Based on this, you will need to use the cast expression. If you don't want this behavior, you can remove the flag --strictNullChecks

.

+3


source


Use user-defined type protection in TypeScrpt:

const arry = [1, 2, 3, 4, "5", 6];
const numArry: number[] = arry
    .filter((i): i is number => {
        return typeof i === "number";
    });
// numArry = [1, 2, 3, 4, 6]

      

Look i is number

in the callback function. This trick gives us the ability to use the Array.filter result type.

+1


source


Decision

Create a guard like:

function isDefined<T>(argument: T | undefined): argument is T {
    return argument !== undefined
}

      

Use it as a type predicate:

const foo: number[] = [1, 2, undefined, 4].filter(isDefined)

      

explanation

Array.prototype.filter

has several overloads. One of them understands that the return value will depend on your predicate function. He uses protection like:

filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];

      

Using proper type protection (rather than using a shortcut and relying on implicit coercion) helps TypeScript to choose this particular overload.

0


source







All Articles