Home > database >  Typescript does not understand filter on undefined values in array of objects
Typescript does not understand filter on undefined values in array of objects

Time:05-03

I struggle to make typescript understand that I'm already handling the undefined cases of one property (project) in this (shortened) example:

interface Entry {
    id: number
    project?: Project
    isPublished?: boolean
}

entries
     .filter(entry => !!entry.project)
     .map(entry => ({
           title: entry.project.title
     }))

(entries is an array of Entrys here) and the compiler complains on the line of entry.project.title on the project property that TS2532: Object is possibly 'undefined' even though I have the filter removing the undefined ones. The other optional property is still optional after the filtering, I thus cannot use Required interface :/

How can I make TS understand the property is definitely defined in the map function?

Thank you

-- Edit: added another optional property / clarification as response to a suggested answer that would make all properties required

CodePudding user response:

You need to define filter callback as a typeguard and use Required type util to make all interface props required:

type Project = {
  title: string
}

interface Entry {
  id: number
  project?: Project
}

type WithProject = Required<Entry>

declare const entries: Entry[]


entries
  .filter((entry): entry is WithProject => !!entry.project)
  .map(entry => ({
    title: entry.project.title
  }))

Playground

If you are interested in more type utils please see docs

CodePudding user response:

Adding onto @captain-yossarian's answer and addressing OP's comment on it, we can make our own utility type to require only a few specific keys:

type Project = {
  title: string
}

interface Entry {
  id: number
  project?: Project
}

type SomeAreRequired<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;

type WithProject = SomeAreRequired<Entry, "project">;

declare const entries: Entry[]

entries
  .filter((entry): entry is WithProject => !!entry.project)
  .map(entry => ({
    title: entry.project.title
  }))

SomeAreRequired picks all keys in T and makes them required, then adds it to T with the K keys omitted, effectively making only the selected keys required.

Playground

  • Related