user5365075
user5365075

Reputation: 2289

Typescript specific type in switch statement

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

Answers (3)

Craig  Hicks
Craig Hicks

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

Saif Ali Khan
Saif Ali Khan

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

Krzysztof Kaczyński
Krzysztof Kaczyński

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'};
  }
}

TypeScript playground - live example

Upvotes: 2

Related Questions