Josu Goñi
Josu Goñi

Reputation: 1274

Distinguish between two types joined with or in TypeScript

When I have a type that can be something or undefined, I can easily use an if statement to check it contains something:

type MaybeString = string | undefined;

function useMaybeString(arg: MaybeString) {
    if (arg !== undefined) {
        printString(arg);
    }
}

function printString(arg: string) {
    console.log(arg)
}

But this example where my type can be one of two posibilities doesn't work:

type Foo = {foo: string};
type Bar = {bar: string};

type FooOrBar = Foo | Bar;

function useFooOrBar(arg: FooOrBar) {
    if (arg.foo !== undefined) {
        console.log(arg.foo);
    } else {
        console.log(arg.bar);
    }
}

What is the solution? And why does TypeScript complain?

Upvotes: 0

Views: 770

Answers (2)

yqlim
yqlim

Reputation: 7080

One of the ways is to explicitly include the other properties into both types, but set them as optional and undefined.

type Foo = {
    foo: string;
    bar?: undefined;
};

type Bar = {
    foo?: undefined;
    bar: string;
};

type FooOrBar = Foo | Bar;

function useFooOrBar(arg: FooOrBar) {
    if (arg.foo !== undefined) {
        console.log(arg.foo);
    } else {
        console.log(arg.bar);
    }
}

Demo in TypeScript playground.

You can also just tell the compiler that the property is there by using Type Guards. However, you will need to check it for every condition, which can be quite cumbersome:

type Foo = {foo: string};
type Bar = {bar: string};

type FooOrBar = Foo | Bar;

function useFooOrBar(arg: FooOrBar) {
    if ('foo' in arg && arg.foo !== undefined) {
        console.log(arg.foo);
    } else if ('bar' in arg && arg.bar !== undefined) {
        console.log(arg.bar);
    }
}

Demo in TypeScript playground.

Upvotes: 0

rsmeral
rsmeral

Reputation: 576

On this line

if (arg.foo !== undefined) {

it complains, because only Foo has the foo property, Bar doesn't have it.

So, at this point, we can't access arg.foo because we don't yet know if arg is Bar or Foo.

Solution

I think you could use User-defined type guards:

const isFoo = (maybeFoo: FooOrBar): maybeFoo is Foo => 
  (maybeFoo as Foo).foo !== undefined;

Then you can use this to assert the type:

function useFooOrBar(arg: FooOrBar) {
  if (isFoo(arg)) {
    console.log(arg.foo);
  } else {
    console.log(arg.bar);
  }
}

Upvotes: 1

Related Questions