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).
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.
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 }>;