Narrowing uncertain values
Narrow with in and instanceof
Use property checks for object unions and constructor checks for real runtime classes.
typeof only distinguishes primitives. Every object answers 'object', so a union of two object shapes needs a different runtime question. You have two: does this property exist, and was this built by that constructor?
Narrow with in
The JavaScript in operator checks whether a property exists on an object or its prototype chain. TypeScript uses that fact to narrow an object union:
type Success = { data: string }
type Failure = { message: string }
function print(result: Success | Failure) {
if ('message' in result) {
console.error(result.message)
return
}
console.log(result.data)
}
Only Failure has a message property, so inside the if, result is a Failure. After the early return, TypeScript knows the remaining code deals with a Success, and result.data compiles.
Without the check, result.data fails:
Property 'data' does not exist on type 'Success | Failure'.
Property 'data' does not exist on type 'Failure'.
One caveat. Optional properties may exist or be absent, so they can appear in both branches. If both variants declare message?: string, the in check proves nothing. When variants overlap, a required discriminant field is clearer. That is the next lesson.
Narrow with instanceof
instanceof checks the prototype chain. It answers “was this value created by this constructor?”:
function logWhen(value: Date | string) {
if (value instanceof Date) {
console.log(value.toISOString())
} else {
console.log(value)
}
}
In the first branch value is a Date, so toISOString() is available. In the else branch it must be a string.
This works for runtime constructors such as Date, Error, URL, and your own JavaScript classes. It cannot work with a type alias or an interface, because those disappear during compilation. Write value instanceof Success and you get:
'Success' only refers to a type, but is being used as a value here.
That error is the compiler reminding you which check you need. When the variants are plain object shapes, reach for in or a discriminant field. When they are class instances, instanceof is the direct, honest check.
Which one I pick
I use in for data that came from JSON, because JSON never carries class instances. I use instanceof in catch blocks, where error instanceof Error is the only way to safely read .message from an unknown error.
Try this: create a union of Date | string, narrow it with instanceof, and format both branches.
Lesson completed