Search code examples
typescriptunion-types

TypeScript: dynamic union type derived from an object value


I want to derive a union type from a table-like input object. I've succeed with a working solution but I suppose there should be shorter / more straightforward way. Here's the input object:

const _COLUMNS = {
  "id": {sortable: true}, // more metadata in real code
  "@fullname": {sortable: true},
  "@profiles": {sortable: false},
  "created_at": {sortable: false},
  ...
} as const

And here's my solution:

type Column = keyof (typeof _COLUMNS)

// ---
type FindSortable<K extends string, T extends {sortable : boolean}> = T extends {sortable : true} ? K : never

type _Sortable = {
  [K in Column] : MakeSortable<K, typeof _COLUMNS[K]>
}

type Sortable = _Sortable[keyof _Sortable] // "id" | "@fullname"
type Order = `${Sortable}:${"asc" | "desc"}` // "id:asc" | "id:desc" | ...
//---

Is there a way to define the above dynamic Sortable type without a middleman _Sortable? Here's the repeating structure:

type Sortable = 
  | FindSortable<"id", typeof _COLUMNS["id"]>
  | FindSortable<"@fullname", typeof _COLUMNS["@fullname"]>
  ...
  | FindSortable<K, typeof _COLUMNS[K]> ??

Or in math-like notation:

type Sortable = FindSortable<K, typeof _COLUMNS[K]> ∀ K in Column ??

Solution

  • Yes, you can immediately look up the property values of the mapped type from _Sortable instead of assigning it to a new type alias:

    type Sortable = {
      [K in Column]: FindSortable<K, typeof _COLUMNS[K]>
    }[Column]
    

    I'm not sure why it matters that you eliminate _Sortable, but if you don't want to define FindSortable or even Column you could do everything in a single type alias:

    type Sortable = typeof _COLUMNS extends infer C ?
      keyof C extends infer K ?
      K extends keyof C ?
      C[K] extends { sortable: true } ?
      K : never : never : never : never;
    
    // type Sortable = "id" | "@fullname"
    

    but that's just silly 😜.

    Playground link to code