The `is` Keyword: A Safe Investigation
The Problem: The Limits of as
In the last lesson, we learned about the as
keyword. It's a powerful command, but it's a gamble. When we're dealing with data from an external API, we can't just assume its shape. We need to prove it.
How can we run an investigation and, if it passes, tell TypeScript that our data is now safe to use?
The Solution: User-Defined Type Guards with is
We can create a special function called a user-defined type guard. This function performs whatever checks we want. Its magic lies in its return signature, which uses the is
keyword. This is called a type predicate.
as
is a COMMAND.is
is a CONCLUSION.
value as User
: You force the type. You take the risk.isUser(value)
: You run an investigation. If the function returnstrue
, TypeScript will treat the value as aUser
from that point onward.
Let's build a type guard to validate our user data.
Step 1: The Function Signature
First, we define our User
type and the signature of our type guard function. Notice the return type data is User
.
type User = {
id: number;
name: string;
email: string;
};
function isUser(data: unknown): data is User {
// Investigation logic will go here
}
That data is User
signature is a promise we're making to TypeScript: "If this function returns true
, I guarantee that the data
object you passed in is a valid User
."
Step 2: Building the Investigation
Now, let's write the logic. A robust check involves multiple steps.
Check 1: Is it a non-null object?
The most basic check. A User
must be an object.
if (typeof data !== 'object' || data === null) {
return false;
}
Check 2: Does it have the right properties?
Now that we know we have an object, we can use the in
operator to check if it has the properties we expect. For the purpose of this lesson, we will only check for the presence of the keys. A more advanced guard would also verify that the values have the correct types, not just the right keys. We will learn how to build these more powerful guards when we cover Generics in a future lesson.
The core principle is the in
check.
Step 3: The Complete Type Guard
Our complete type guard will first check if the data is an object, and then use the in
operator to ensure all the required keys exist.
When we combine these checks for all our properties, we get a complete, robust type guard. We can make it more readable by assigning each check to a variable.
💡 Hint: How would you start checking value types?
As our warning mentioned, a truly robust guard also checks the types of the values. How would you start doing that?
You can use the same two-part handshake pattern we learned earlier. Here's how you could check just the id
property's type:
function isUserWithIdTypeCheck(data: unknown): data is User {
if (typeof data !== 'object' || data === null) {
return false;
}
// This is the key!
const hasTypedId = 'id' in data && typeof (data as { id: unknown }).id === 'number';
const hasName = 'name' in data;
const hasEmail = 'email' in data;
return hasTypedId && hasName && hasEmail;
}
const userWithWrongType = { id: "123", name: "Kevin", email: "[email protected]" };
console.log("--- Checking with Type Check ---");
console.log("Checking userWithWrongType:", isUserWithIdTypeCheck(userWithWrongType)); // false!
This is the foundation we will build upon in the Generics module to create fully robust, reusable type guards.
💡 Hint: How would you start checking value types?
As our warning mentioned, a truly robust guard also checks the types of the values. How would you start doing that?
You can use the same two-part handshake pattern we learned earlier. Here's how you could check just the id
property's type:
function isUserWithIdTypeCheck(data: unknown): data is User {
if (typeof data !== 'object' || data === null) {
return false;
}
// This is the key!
const hasTypedId = 'id' in data && typeof (data as { id: unknown }).id === 'number';
const hasName = 'name' in data;
const hasEmail = 'email' in data;
return hasTypedId && hasName && hasEmail;
}
const userWithWrongType = { id: "123", name: "Kevin", email: "[email protected]" };
console.log("--- Checking with Type Check ---");
console.log("Checking userWithWrongType:", isUserWithIdTypeCheck(userWithWrongType)); // false!
This is the foundation we will build upon in the Generics module to create fully robust, reusable type guards.
Conclusion: as
vs. is
- A Clear Winner
You have now learned the difference between the two ways of narrowing unknown
types.
as
: A risky command. Use it sparingly.is
: A safe investigation. This is the professional's choice for validating external data.
In the final lesson of this module, we will put our new isUser
type guard to use in a real fetch
request.