简体   繁体   中英

TypeScript template literal string from nested object

I want to get a union of all the keys under states in this object type,

I have a nested object of state keys. I want to get a union of all these keys under state in dot notation.

For example, for this config:

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

I want 'idle' | 'running' | 'paused' | 'paused.frozen' 'idle' | 'running' | 'paused' | 'paused.frozen'

Is this possible? Any ideas?

Looks like another job for recursive conditional types as well as template literal types :

type StatesKeys<T> = T extends { states: infer S } ? {
  [K in Extract<keyof S, string>]: K | `${K}.${StatesKeys<S[K]>}`
}[Extract<keyof S, string>] : never

type ConfigStatesKeys = StatesKeys<Config>;
// type ConfigStatesKeys = "idle" | "running" | "paused" | "paused.frozen"

StatesKeys<T> inspects T for its states property S , and generates for each of its keys K the union we want, which is K itself, plus the possible concatenation of K with a dot and StatesKeys<S[K]>> . That is, we are concatenating each key K with any nested keys from S[K] . If there are no nested keys, and StatesKeys<S[K]> is never , the template literal will also become never , so we don't have to special-case it.

Playground link to code

You can do this with a recursive conditional type like this:

type StateKeys<T> = T extends {states: infer S}
  ? keyof S | StateKeys<S[keyof S]>
  : never

type Test = StateKeys<Config>
// type Test = "idle" | "running" | "paused" | "frozen"

TypeScript playground

Ah, I missed that you needed paused.frozen instead of just frozen . For what it's worth, my old solution could be fixed like this, using just conditional types:

type StateKeysForKey<S, K> = K extends keyof S & string
  ? `${K}.${StateKeys<S[K]>}`
  : never

type StateKeys<T> = T extends {states: infer S}
  ? keyof S | StateKeysForKey<S, keyof S>
  : never

type Test = StateKeys<Config>
// type Test = "idle" | "running" | "paused" | "paused.frozen"

TypeScript playground

You can use keyof keyword, in your particular example one solution could be keyof states

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM