Code Review Videos > JavaScript / TypeScript / NodeJS > What If Your Object Could Be More Than One Type in TypeScript?

What If Your Object Could Be More Than One Type in TypeScript?

In TypeScript, imagine you have a situation where you have an object that could be one of several different types.

How do you explain to TypeScript that the object you are working with is a specific type?

Not very clear? OK. Let’s see some code, and then it will likely / hopefully make more sense.

type A = { a: true };
type B = { b: false };

function someFunction(): A | B {
  // Randomly returns an object of type A or B
  return Math.random() > 0.5 ? { a: true } : { b: false };
}

const result: A | B = someFunction();

if (result.b) {
   // ts2339 property b does not exist on type A
}Code language: TypeScript (typescript)

You can play with this at TypeScript playground:

The error happens because we told TypeScript that result could be an object of either type A or type B.

The error is saying that if result is some object of type A then it won’t have a property of b, so we have probably made a mistake writing this code.

The Fix

The solution here is to narrow the type.

We can do that by checking whether a property is in the given object, or its prototype chain.

This isn’t TypeScript – the in operator is in JavaScript (backend and frontend) and has been for a very long time.

Here’s how we could fix the code above by applying type narrowing using the in operator:

type A = { a: true };
type B = { b: false };

function someFunction(): A | B {
  // Randomly returns an object of type A or B
  return Math.random() > 0.5 ? { a: true } : { b: false };
}

const result: A | B = someFunction();

if ('b' in result) {
  // Now TypeScript knows x is of type B
  console.log(result.b); // No error, since x is B
}Code language: TypeScript (typescript)

That satisfies TypeScript that we have checked the property will exist before trying to access it.

If you need to make this check in more than one place, you can extract this conditional out into a more fully fledged ‘type guard’ function:

type A = { a: true };
type B = { b: false };

function someFunction(): A | B {
  return Math.random() > 0.5 ? { a: true } : { b: false };
}

// a type guard function
function isB(myObject: A | B): myObject is B {
  return 'b' in myObject;
}

const result: A | B = someFunction();

if (isB(result)) {
  // Now TypeScript knows x is of type B
  console.log(result.b); // No error, since x is B
}Code language: TypeScript (typescript)

An example from this week where I’ve used this in the real world was with an API response.

When calling the API, I could either get back a 200 with a nicely formed response containing lots of different, useful properties.

Or I would get back a 200 response but with an error object.

Something like {success: false, message: "it all went wrong!"}.

Both are valid bits of JSON, but I first needed to help TypeScript understand which of the two response shapes I was dealing with.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.