Given the following code:
interface Obj {
foo: string;
bar: number;
}
type ObjKey = keyof Obj;
type ObjVal<Key extends ObjKey> = Obj[Key];
type ObjTuple<Key extends ObjKey> = [Key, ObjVal<Key>];
I want to create a function that takes in a tuple (of unknown type), and discriminates the Key
part (at index 0), to figure out what the value type is:
function doThings(tuple: ObjTuple): string {
switch (tuple[0]) {
case "bar":
return tuple[1].toFixed();
case "foo":
return tuple[1];
}
}
Of course, this doesn't work, as the type argument for ObjTuple
is missing. Since I don't want to specify it (the function should figure out what type it is), I changed the declaration to have ObjKey
as default:
type ObjTuple<Key extends ObjKey = ObjKey> = [Key, ObjVal<Key>];
Sadly, the discrimination is not working at all. TypeScript correctly detects the allowed variants of the key (e.g. tells me when a code path is missing, or when the switch contains an impossible key), but the value is always a union of all the variants.
Here is a link to a playground, with the final code.
CodePudding user response:
The problem here is the type of ObjVal
. The editor is not doing us a favor when displaying it but here is how the expanded type looks like:
type ObjTuple = ["foo" | "bar", string | number]
That does not look right at all since there is not correlation between specific properties and types anymore. Both elements are just a union.
We rather want a type which looks like this:
type ObjTuple = ["foo", string] | ["bar", number]
And we can generate this one using ObjTuple
with one modification. We need to distribute over Key
.
type ObjTuple<Key extends ObjKey = ObjKey> = Key extends Key
? [Key, ObjVal<Key>]
: never
This should solve the issue.