Search code examples
typescriptgenericsmapped-types

How to filter a parent type in order to pick the children of a given type in TypeScript?


What I need

I have an undetermined number of objects in a key-value parent object. Each child has a property namespaced of type boolean:

const obj1 = {
  namespaced: true,
  a() {}
} as const

const obj2 = {
  namespaced: false,
  b() {}
} as const

const parentObj = {
  obj1,
  obj2
} as const

Is it possible to make a mapped type which contains only objects with namespaced set to true (and the same with false)?

What I tried

interface ParentObj {
  [name: string]: Obj
}

interface Obj {
  namespaced: boolean
}

type FilterNamespaced<P extends ParentObj> = {
  [O in keyof P]: P[O] extends { namespaced: true } ? P[O] : never
}

type FilterNotNamespaced<P extends ParentObj> = {
  [O in keyof P]: P[O] extends { namespaced: false } ? P[O] : never
}

type F1 = FilterNamespaced<typeof parentObj>

type F2 = FilterNotNamespaced<typeof parentObj>

It is almost done but F1 still contains a key obj2 of type never, and F2 contains a key obj1 too. I would like to have the same thing but without these keys.


Solution

  • You can do it, but you need an extra level of indirection. You need to filter the keys first and then pick them from the original type. Using something like KeyOfType from here will get you to where you want:

    
    type KeyOfType<T, U> = { [P in keyof T]: T[P] extends U ? P : never }[keyof T]
    
    type FilterNamespaced<P extends ParentObj> = Pick<P, KeyOfType<P, { namespaced: true }>>
    
    type FilterNotNamespaced<P extends ParentObj> = Pick<P, KeyOfType<P, { namespaced: false }>>
    

    Play