I have played with typescript generics recently and found the behaviour that i'm not sure how to explain, on the begging i have:
Enum list with initial values
Couple of classes that present results, one class correspond exactly one enum value
So the task is write the function which maps initial values to result values (simple switch case) but it wasn't enough for me, because types is this case is something like:
1 enum => any of the result classes
so i proceeded with generics, create type map of initial enums to result values, made generic function, which works fine in this case, type is fine now:
1 enum => 1 result class
but the problem is that typescript don't want to recognise types of returned values in this case so i cast types to something like any, there is a minimal playground and the code if link expired:
enum TestKind {
String = 'str',
Number = 'num',
Boolean = 'bool',
}
type TestKindTypeMap = {
[TestKind.String]: string;
[TestKind.Number]: number;
[TestKind.Boolean]: boolean;
};
function transformerWithBadTyping(input: TestKind): TestKindTypeMap[TestKind] {
switch (input) {
case TestKind.String:
return '';
case TestKind.Number:
return 0;
case TestKind.Boolean:
return false;
default:
throw new Error();
}
}
console.log(transformerWithBadTyping(TestKind.String))
console.log(transformerWithBadTyping(TestKind.Number))
console.log(transformerWithBadTyping(TestKind.Boolean))
function transformer<K extends TestKind>(input: K): TestKindTypeMap[K] {
switch (input) {
case TestKind.String:
return '';
case TestKind.Number:
return 0 as any;
case TestKind.Boolean:
return false as TestKindTypeMap[K];
default:
throw new Error();
}
}
console.log(transformer(TestKind.String))
console.log(transformer(TestKind.Number))
console.log(transformer(TestKind.Boolean))
so the questions are:
Typescript cannot narrow a type which comes from a generic parameter, a solution to workaround would be to recreate the type map in the value space and return one of its members:
function transformer<K extends TestKind>(input: K): TestKindTypeMap[K] {
const map = {
[TestKind.String]: '',
[TestKind.Number]: 0,
[TestKind.Boolean]: false
};
return map[input];
}