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.