BaronVonKaneHoffen
BaronVonKaneHoffen

Reputation: 1990

How do I work with optional object properties in Typescript union types?

So take this example:

type GeneralAPIError = {
  status: "error";
  message: string;
};

type Foo = {
    status: string;
    whatever: string;
}

function wat(): Foo | GeneralAPIError {
    return Math.random() > .5 ? {
        status: "error",
        message: "noooo"
    } : {
        status: "success",
        whatever: "yayyy"
    };
}

const eh = wat();

// This doesn't work
console.log(eh.whatever || eh);

// Neither does this
if(eh.hasOwnProperty("whatever")) {
    console.log(eh.whatever)
} else {
    console.log(eh);
}

// This does, but the "as" is pretty ugly
if(eh.hasOwnProperty("whatever")) {
    console.log((eh as Foo).whatever);
} else {
    console.log(eh);
}

What's the best way to declare the return type of wat() ? In real life it makes an API call that can return one shape of JSON for an error (type GeneralAPIError) and a different one for success (type Foo). The error type is reused for lots of similar functions.

The code at the end to say "if an object has a certain key, do this with it" works fine functionally, but TS says:

Property 'whatever' does not exist on type 'GeneralAPIError | Foo'. Property 'whatever' does not exist on type 'GeneralAPIError'.

for the first two examples. It feels like I should be able to type the function return so eh.whatever || eh works. What am I missing?

Upvotes: 0

Views: 32

Answers (1)

Szaman
Szaman

Reputation: 2388

You need a user-defined type guard to check what the type is.

type GeneralAPIError = {
    status: "error";
    message: string;
};

type Foo = {
    status: string;
    whatever: string;
}

/**
* this type guard checks if the element is of type Foo.
*/
function isFoo(wat: Foo | GeneralAPIError): wat is Foo {
  return (wat as Foo).whatever !== undefined;
}

function wat(): Foo | GeneralAPIError {
    return Math.random() > .5 ? {
        status: "error",
        message: "noooo"
    } : {
        status: "success",
        whatever: "yayyy"
    };
}

const eh = wat();
console.log(isFoo(eh) ? eh.whatever : eh);

Another approach, if you have a simple case, is to use the in operator:

type GeneralAPIError = {
    status: "error";
    message: string;
};

type Foo = {
    status: string;
    whatever: string;
}

function wat(): Foo | GeneralAPIError {
    return Math.random() > .5 ? {
        status: "error",
        message: "noooo"
    } : {
        status: "success",
        whatever: "yayyy"
    };
}

const eh = wat();
console.log("whatever" in eh ? eh.whatever : eh);

Upvotes: 1

Related Questions