typescripttypescript-generics

TypeScript: Create a new type from a nested object values


I'm trying to create a new type from nested object value but if the key is not present on any of the nested levels, it is failing, Where I'm going wrong?


const events = [
  { name: 'foo' },
  { name: 'bar' },
  { value: 'baz' }
] as const;


type Events = typeof events;

type AllValuesOf<T> = T extends any ? T[keyof T] : never;

// Error: Property 'name' does not exist on the type 
type Name = AllValuesOf<Events>['name'] // ideally I want this to be 'foo' | 'bar'

Expected

type Name = "foo" | "bar"

Ts Playground link


Solution

  • It looks like you'd like to take Events and produce a type of the following form, programmatically:

    type AllEvents = {
        name: "foo" | "bar";
        value: "baz";
    }
    

    That way you could index into it with "name" to get the union "foo" | "bar", or index into it with "value" to get "baz".

    If we start with a union T of object types (expected to be Events[number], the union of elements of events), then we first need to find the full set of its keys. We can do this by distributing the keyof operator over unions in T:

    type AllKeys<T> = T extends unknown ? keyof T : never;
    

    Then we can Combine them into a single object type. For each key K in AllKeys<T>, we filter the T union for just those members with key K, and grab the value type at that key. This is another job for distributive conditional types, also using the infer keyword to do the "grabbing":

    type Combine<T> = 
      { [K in AllKeys<T>]: T extends Record<K, infer V> ? V : never };
    

    Put that together and we get the desired AllEvents type:

    type AllEvents = Combine<Events[number]>;
    /* type AllEvents = {
        name: "foo" | "bar";
        value: "baz";
    } */
    

    And you can index into it as desired:

    type Name = AllEvents['name'];
    //type Name = "foo" | "bar"
    

    Playground link to code