Change Function Returns Based on Type
For this solution, we'll start with getting the tests to pass and then work on the types.
Inside of the coerceAmount
function we can check if amount.amount
exists, then return it. Otherwise, return amount
:
const coerceAmount = (amount: number | { amount: number }) => {
if (amount.amount) {
return amount.amount;
}
return amount;
};
Now our tests will pass, but TypeScript gives us an error.
Hovering over amount.amount
tells us:
Property 'amount' does not exist on type number.
This is a TypeScript quirk where you can't access a property unless you know it's there.
Check Amount's Type
One way to fix the type error is updating our if
to check if the typeof
amount is an object. If so, return amount.amount
otherwise just return amount
.
const coerceAmount = (amount: number | { amount: number }) => {
if (typeof amount === 'object') {
return amount.amount;
}
return amount;
};
This would also work by checking if typeof
amount is number, then return the amount. Otherwise, return amount.amount
:
const coerceAmount = (amount: number | { amount: number }) => {
if (typeof amount === 'number') {
return amount;
}
return amount.amount;
};
Using the typeof
operator to narrow down different branches of a union type is really powerful technique.
It's also really useful when interfacing with APIs that you didn't create.
Transcript
For this one, I'm actually going to try and fixing it live so I can show you the problems that you might have run into. CoerceAmounts. Let's just try and get the test passing first. We can say if amount.amount, which represents our object, then we can return amount.amount, otherwise return amount. Are you sick of seeing the word amount yet?
Now our tests are passing, but our types are not passing. Because amounts here it's saying property amount does not exist on type number or amount number. This is a funny little quirk of TypeScript, which is you can't access a property unless you know it's there. It's going to be strict with you on that.
What that means that you should do is we need to write this in reverse. We need to check for the number here and then return the object in the second branch. You can do this with...Matter of fact, we can do it this way too. You can do this with typeof.
Now typeof, if typeof amount === "object", then what we can do is it then knows which branch of this union it's looking at. It's saying if typeof amount === "object", then we know that it's this one. Because if we ran typeof, and it was in this branch, then it would return number. Actually that means that you can hover over this and it will show you that amount.
Whereas down here, it's now typed as amount number. You can do this in reverse too, which is what I did for the solution. If typeof amount === "number", then return amount, There. This typeof operator, the idea that you can narrow TypeScript down and get it to understand two different branches of a union just with a typeof, is really powerful.
It's really powerful for interfacing with APIs that you didn't create as well.