Typescript filter showing error is not assignable to type

Viewed 1276

This is not working. Shows error Type '(Person | null)[]' is not assignable to type 'Person[]'. Type 'Person | null' is not assignable to type 'Person'. Type 'null' is not assignable to type 'Person'.

interface Person {
  name: string;
}

function filterPersons(persons: Array<Person | null>): Array<Person> {
    return persons.filter(person => person !== null)
}

function run() {
    const persons: Array<Person | null> = []
    persons.push(null)
    filterPersons(persons)
}

run()

But this is working

interface Person {
  name: string;
}

function filterPersons(persons: Array<Person | null>): Array<Person> {
    return persons.filter(person => person !== null) as Array<Person>
}

function run() {
    const persons: Array<Person | null> = []
    persons.push(null)
    filterPersons(persons)
}

run()

Any explanation & are there any better solution? Thanks ️

3 Answers

The first piece of code persons.filter(person => person !== null) does not typecheck because TSC is unable to understand that your code is actually narrowing array item type to be Person.

You can help it by declaring your filter function as a type guard. Playground.

interface Person {
  name: string;
}

// notice person is Person return type
const isPerson = (person: Person | null) : person is Person => person !== null

function filterPersons(persons: Array<Person | null>): Array<Person> {
    return persons.filter(isPerson)
}

function run() {
    const maybePersons: Array<Person | null> = []
    maybePersons.push(null)
    const persons: Person[] = filterPersons(maybePersons)
    console.log(persons)
}

run()

You could use the omni-powerful Array.flatMap to do your filtering instead of filter... you can implement most array transformations in terms of flatMap. However, it doesn't read amazingly well if you're not familiar with flatMap operations.

The trick with flatMap to turn it into a filter is to return an empty array for items you don't want, and a 1-item array for those that you do want:

so:

interface Person {
  name: string;
}

function filterPersons(persons: Array<Person | null>): Array<Person> {
    return persons.flatMap(p => p === null ? [] : [p]) // Array<Person> yay!
}

function run() {
    const persons: Array<Person | null> = []
    persons.push(null)
    filterPersons(persons)
}

run()

playground link

Related