cassiozen
cassiozen

Reputation: 269

TypeScript keyof of nested object type

I want to get a union of all the keys under on in this object type inferred from a configuration object:

type Config = {
initial: string;
states: {
    idle: {
        on: {
          START: string;
        };
        effect(): void;
    };
    running: {
        on: {
            PAUSE: string;
        };
        effect(): void;
    };
    paused: {
        initial: string;
        states: {
            frozen: {
                on: {
                    HEAT: string;
                };
            };
        };
        on: {
          RESET: string;
        };
    };
};

}

Note that the configuration can have nested on keys under states. Right now I can get the first level keys using:

type KeysOfTransition<Obj> = Obj extends Record<PropertyKey, any> ? keyof Obj : never;
type TransitionKeys = KeysOfTransition<Config["states"][keyof Config["states"]]["on"]>
// "START" | "PAUSE" | "RESET"

But I cannot get HEAT in the union, which is nested. Any ideas?

Upvotes: 4

Views: 3405

Answers (1)

jcalz
jcalz

Reputation: 327754

There may well be edge cases, but here's an implementation that works for your example:

type KeysUnder<T, K extends PropertyKey> =
  T extends object ? {
    [P in keyof T]-?: (P extends K ? keyof T[P] : never) | KeysUnder<T[P], K>
  }[keyof T] : never;
    
type ConfigOnKeys = KeysUnder<Config, "on">;
// type ConfigOnKeys = "START" | "PAUSE" | "RESET" | "HEAT"

The idea is to use a recursive conditional type to drill down into object properties. To get KeysUnder<T, K>, for an object T, we look at each property key P. If P matches K (which will be "on" in your use case), then you want the keys of its the property value type T[P]. The recursive bit is that we also join all these keys to KeysUnder<T[P], K>.

Playground link to code

Upvotes: 8

Related Questions