RTYX
RTYX

Reputation: 1334

Conditional properties and switch statements in TypeScript

I defined these types:

type ItemWithName = {
  type: "ItemWithName";
  name: string;
  a: { name: string } ;
};

type ItemWithValue = {
  type: string;
  name: string;
  b: { value: number } ;
};

export type Item = ItemWithName | ItemWithValue;

And I have a function to extract a certain value from an object:

const extractValue = (item: Item) => {
  switch (item.type) {
    case "ItemWithName":
      return item.a.name; // TS2339: Property 'a' does not exist on type 'Item'.
    case "ItemWithValue":
      return item.b.value; 
  }
};

But I get this error: TS2339: Property 'a' does not exist on type 'Item'.

I'm not entirely sure how to make TypeScript understand that if the type of the object is "ItemWithValue", there's a property a.

Note: this is a small abstraction of my particular issue, in reality I have several more of these types.

The expected behaviour is that TypeScript detects that the item is of type ItemWithName if the object has a type value of "ItemWithName"

Upvotes: 0

Views: 842

Answers (1)

Jupiter
Jupiter

Reputation: 116

Unfortunately this doesn't currently work with Typescript.

Here's an issue on GitHub about a similar problem.

type-fest has a wrapper type LiteralUnion for the problem referenced in the issue. However you can't just use it like type Item = LiteralUnion<ItemWithName, ItemWithValue>.

So these are my two possibilities at the moment:

  1. Possibility: simply cast the type
const extractValue = (item: Item) => {
  switch (item.type) {
    case "ItemWithName":
      const itemWithName = item as ItemWithName;
      return itemWithName.a.name;
    case "ItemWithValue":
      return item.b.value; 
  }
};
  1. Possibility: list all possible types

Note: this is a small abstraction of my particular issue, in reality I have several more of these types.

Instead of using

type ItemWithValue = {
  type: string;
  name: string;
  b: { value: number } ;
};

you could create a type which unions all possible types like

type ItemWithValue = {
  type: 'ItemWithValue' | 'OtherItemWithValue' | 'AgainAnItemWithValue';
  name: string;
  b: { value: number } ;
};

It would be really cool to use type: Exclude<string, 'ItemWithName'>. But this also would not work as you can see in this discussed question.

Upvotes: 2

Related Questions