Home > Mobile >  Extracting Type from TypeScript Union
Extracting Type from TypeScript Union

Time:06-28

I have a union type, PromptOptions:

type PromptOptions =
  | BasePromptOptions
  | BooleanPromptOptions
  | StringPromptOptions

type BasePromptOptions = {
  kind: string | (() => string)
};

type BooleanPromptOptions = { kind: 'confirm' };

type StringPromptOptions = {
  kind: 'input' | 'invisible' | 'list' | 'password' | 'text';
};

What I'm trying to do:

Given an arbitrary type, type Lookup = { kind: 'invisible' }, I want to use type ExtractedType<T> = Extract<PromptOptions, T> with the end result of ExtractedType<Lookup> = StringPromptOptions.

This works if I pass in a type that exactly matches a prompt option (ExtractedType<{ kind: 'confirm' }> = BooleanPromptOptions), but something like this: ExtractedType<{ kind: 'invisible' }> = never when I want/expect it to be StringPromptOptions.

Clearly this isn't correct, but I want to do something like Extract<PromptOptions, T extends <PromptOptions['kind']>>, I'm just not sure how (or if this is even possible).

Playground Link

CodePudding user response:

After fiddling around for half an hour, I think this is not really possible.

Anyway, here is the closest I got:

type ExtractedType<T> = {
  [K in PromptOptions as T extends K 
    ? K[keyof K] extends infer U extends string 
      ? U
      : "0"
    : never
  ]: K
} extends infer U ? U[keyof U] : never

It passes the test cases for this example:

type T0 = ExtractedType<{ kind: 'confirm' }>;
//   ^? BasePromptOptions | BooleanPromptOptions


type T1 = ExtractedType<{ kind: 'invisible' }>;
//   ^? BasePromptOptions | BooleanPromptOptions


type T2 = ExtractedType<{ kind: string }>
//   ^? { kind: string | (() => string); }


type T3 = ExtractedType<{ kind: () => string}> 
//   ^? { kind: string | (() => string); }

Note that T0 and T1 also get BasePromptOptions as a result. This is because 'invisible' and 'confirm' both extend { kind: string }.

Also note that this will break for more complex scenarios. Try adding another type like BasePromptOptions which only has a string and a function as type.

Playground


Also theoratically, a solution may be possible by converting the union to a tuple using TuplifyUnion from here. But this type should not be used as it kind of breaks the type system and is not reliable. So I haven't bothered trying it.

CodePudding user response:

I was able to solve this after realizing that PromptOptions was a discriminating union via the kind field.

I was then able to use the DiscriminateUnion type from this answer to get the correct type out of PromptOptions:

type DiscriminateUnion<T, K extends keyof T, V extends T[K]> = T extends Record<
  K,
  V
>
  ? T
  : T extends Record<K, infer U>
  ? V extends U
    ? T
    : never
  : never;

type IsBooleanPromptOptions = Extract<DiscriminateUnion<PromptOptions, 'kind', 'confirm'>, { kind: string }>;

type IsStringPromptOptions = Extract<DiscriminateUnion<PromptOptions, 'kind', 'list'>, { kind: string }>;

Playground

  • Related