Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Build union of keys in recursive object with specific shape

I’m trying to extract the ACTION_ keys from the following config shape (it’s a state machine). Notice that a state can have initial, states, then any number of actions:

export const config = {
  initial: 'STATE_1',
  states: {
    STATE_1: {
      ACTION_5: 'STATE_2',
      initial: 'STATE_3',
      states: {
        STATE_3: {
          ACTION_1: 'STATE_4',
          ACTION_2: 'STATE_2',
        },
        STATE_4: {
          ACTION_3: 'STATE_3',
        },
      },
    },

    STATE_2: {
      ACTION_4: 'STATE_1',
      ACTION_5: 'STATE_3',
      initial: 'STATE_5',
      states: {
        STATE_5: {
          ACTION_6: 'STATE_6',
        },
        STATE_6: {
          ACTION_6: 'STATE_5',
        },
      },
    },
  },
};

The best I can come up with at the moment is this:

type ActionUnion<T> = T extends { [K in keyof T]: infer U }
  ? U extends { states: any }
    ? ActionUnion<U['states']> // recursive call for nested states
    : U extends { [key: string]: any }
      ? keyof Omit<U, 'initial' | 'states'> // extract action keys
      : never
  : never;

But this is inferred as "STATE_1" | "STATE_2".

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

type T1 = ActionUnion<typeof config>; // should be "ACTION_1" | "ACTION_2" | etc...

I think I’m probably making this more complex than it needs to be.

Playground Link

>Solution :

Utilities:

Prettify – simplifies the type shown by the IDE:

type Prettify<T> = T extends infer R ? { [K in keyof R]: R[K] } : never;

Next, using the mapped types we will map through the object and check if the key of it starts with ACTION_ then we pick the key, otherwise if the property is an object, we recursively check in the same manner the keys of that property, if none of the previous conditions are true, we just assign never. Then we extract the values of that object to get the union of values:

type ActionUnion<T extends object> = Prettify<
  {
    [K in keyof T]: K extends `ACTION_${string}`
      ? K
      : T[K] extends object
      ? ActionUnion<T[K]>
      : never;
  }[keyof T]
>;

Usage:

// type Actions = "ACTION_4" | "ACTION_5" | "ACTION_6" | "ACTION_3" | "ACTION_1" | "ACTION_2"
type Actions = ActionUnion<typeof config>;

playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading