Last updated: Feb 28, 2024
Reading timeยท3 min
Use a user-defined type guard to check if an object implements an interface in TypeScript.
The user-defined type guard consists of a function that checks if the passed-in object contains specific properties and returns a type predicate.
interface Employee { id: number; name: string; salary: number; } function isAnEmployee(obj: any): obj is Employee { return 'id' in obj && 'name' in obj && 'salary' in obj; } const emp: Employee = { id: 1, name: 'Bobby Hadz', salary: 100, }; console.log(isAnEmployee(emp)); // ๐๏ธ true console.log(isAnEmployee({ id: 1 })); // ๐๏ธ false if (isAnEmployee(emp)) { // ๐๏ธ TypeScript knows that emp is type Employee console.log(emp.id); // ๐๏ธ 1 console.log(emp.name); // ๐๏ธ "Bobby Hadz" console.log(emp.salary); // ๐๏ธ 100 }
We used a user-defined type guard to check if an object implements an interface.
obj is Employee
syntax is a type predicate where obj
must be the name of the parameter the function takes.If the isAnEmployee
function returns true
, TypeScript knows that the
supplied value is of type Employee
and allows us to access all properties and
methods on the specific interface.
The example simply checks if the passed-in object contains the id
, name
and
salary
properties.
function isAnEmployee(obj: any): obj is Employee { return 'id' in obj && 'name' in obj && 'salary' in obj; }
This can get pretty verbose if your interface has many properties.
type
propertyAn alternative approach is to add a type
property to the interface, for which
you check instead.
interface Employee { id: number; name: string; salary: number; type: 'Employee'; // ๐๏ธ add type property } function isAnEmployee(obj: any): obj is Employee { // ๐๏ธ check for type property return 'type' in obj && obj.type === 'Employee'; } const emp: Employee = { id: 1, name: 'Bobby Hadz', salary: 100, type: 'Employee', }; console.log(isAnEmployee(emp)); // ๐๏ธ true console.log(isAnEmployee({ id: 1 })); // ๐๏ธ false if (isAnEmployee(emp)) { console.log(emp.id); // ๐๏ธ 1 console.log(emp.name); // ๐๏ธ "Bobby Hadz" console.log(emp.salary); // ๐๏ธ 100 console.log(emp.type); // ๐๏ธ "Employee" }
The Employee
interface has a type
property with the value of Employee
.
This means that all objects that have a type of Employee
will have this
property.
type
property that's equal to Employee
.I've also written an article on how to create an object based on an interface.
However, note that this can get difficult to manage if you have interfaces that
extend from Employee
.
interface Employee { id: number; name: string; salary: number; type: 'Employee'; // ๐๏ธ add type property } // โ๏ธ Error: Interface 'Accountant' incorrectly // extends interface 'Employee'. // Types of property 'type' are incompatible. interface Accountant extends Employee { type: 'Accountant'; }
You can't simply override the type
property in the Accountant
interface.
If you have to do this, you'd get to set the type
property to be a string
,
but this would be difficult to manage if you have deeply nested structures.
User-defined type guards are very useful, especially when you have to check if an object is one of multiple types you know about in advance.
interface Dog { bark(): void; } interface Cat { meow(): void; } const dog: Dog = { bark() { console.log('woof'); }, }; const cat: Cat = { meow() { console.log('meow'); }, }; function isDog(pet: Dog | Cat): pet is Dog { return 'bark' in pet; } function getPet(): Dog | Cat { return Math.random() > 0.5 ? dog : cat; } const pet = getPet(); if (isDog(pet)) { console.log(pet.bark()); } else { // ๐๏ธ TypeScript knows pet is Cat console.log(pet.meow()); }
The isDog()
function in the example takes a parameter of type Dog
or Cat
and checks if the passed-in parameter is a Dog
.
Notice that we can access dog-specific properties in the if
block and in the
else
block, TypeScript knows that if pet
isn't a Dog
, then it will be of
type Cat
.
I've also written an article on how to check if a property exists in an object.
If you need to get an object's key by value, click on the following link.