Reputation: 2289
I'm trying to write a function which, given an enum value, returns a specific type. Typescript isn't recognizing the properties inside switch and if statements.
interface A {
a: string;
}
interface B {
b: string;
}
enum DataType {
a = 'a',
b = 'b',
}
interface Type {
[DataType.a]: A;
[DataType.b]: B;
}
function test<T extends keyof Type>(type: T): Type[T] {
switch (type) {
case DataType.a:
return {a: 'test'}; // <--- no error, but TS doesn't recognize the property
}
}
Using Typescript 4.2.3
.
Upvotes: 3
Views: 6406
Reputation: 2538
When you have a group of Types that are related, and you often need to distinguish between members of that related group, then generally it is best to use a dedicated property to achieve that - TypeScript is optimized for that approach. For example
type KindValues = "a" | "b"
type TT = {
kind:KindValues,
body: any
}
type TA = {
kind:"a",
body:string
}
function isTA(t:TT):t is TA { return t.kind==="a"; }
type TB = {
kind:"b",
body:number
}
function isTB(t:TT):t is TB { return t.kind==="b"; }
function handler(t:TT):void{
if (isTA(t)){
//t.body=1 // error: number is not assignable to string
t.body = 'x';
}
else if (isTB(t)){
//t.body='x' // error: string is not assignable to number
t.body = 1;
}
}
If you want to avoid a waterfall of type checks then usually it is most flexible to create a Map from kind
to appropriate functions.
Typescript does not require a single discriminator key. It still works well with a second key for subtyping types, but the fewer the better it works, in terms of auto-completion.
Upvotes: 0
Reputation: 566
It seems like you cannot achieve what you want with a switch statement because of the typescript limitations. But the following solution should absolutely work for you.
interface A {
a: string;
}
interface B {
b: string;
}
enum DataType {
a = "a",
b = "b",
}
interface Type {
[DataType.a]: A;
[DataType.b]: B;
}
function test<T extends keyof Type>(type: T): Type[T] {
return (type === DataType.a ? { a: "test" } : { b: "test" }) as Type[T];
}
Upvotes: 1
Reputation: 5071
I do not know it is acceptable for you, but function overloads seems to work. If you decide to go with this solution, you will need to replace interface Type
with a special overload signature for each case.
interface A {
a: string;
}
interface B {
b: string;
}
enum DataType {
a = 'a',
b = 'b',
}
function test(value: DataType.a): A;
function test(value: DataType.b): B;
function test(value: DataType): A | B {
switch (value) {
case DataType.a:
return {a: 'testA'};
case DataType.b:
return {b: 'testB'};
}
}
Upvotes: 2