- Saved searches
- Use saved searches to filter your results more quickly
- Compile-time interface checking #16510
- Compile-time interface checking #16510
- Comments
- Typescript check interface implementation
- # Check if an Object implements an interface in TypeScript
- # Check if an Object implements an interface by using a type property
- # Working around interfaces that extend from other interfaces
- # A user-defined type guard is a better solution
- How to check if an object implements an interface in Typescript
- Check the object data type by Type Guards
- “in” Type Guards
- instanceof interface
- User defined Type Guards
- Failed trial by Abstract class
- Check if the variable is NodeJS.ErrnoException type
- Strict Object Type Check
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compile-time interface checking #16510
Compile-time interface checking #16510
Comments
How can I check if object implements is implementation of some interface?
Please, add some instructions for compile-time interface checks.
TypeScript Version: 2.4.0
interface IFirst < f1(): void; >interface ISecond < f2(): void; >interface IThird < f3(): void; >class Implementation implements IFirst, ISecond, IThird < f1() < console.info('+f1'); >f2() < console.info('+f2'); >f3() < console.info('+f3'); >> class Incorrect implements ISecond < f1(x: number): void < console.info('!f1(' + x + ')') >f2() < console.info('+f2'); >f3: number = 10; > function testObject(obj: IFirst | ISecond | IThird) < try < let x1: IFirst = obj; x1.f1(); > catch (e) < console.error(e); >try < let x2: ISecond = obj; x2.f2(); > catch (e) < console.error(e); >try < let x3: IThird = obj; x3.f3(); > catch (e) < console.error(e); >> testObject(new Implementation()); testObject(new Incorrect());
Expected behavior:
Usual static type checks does not work to check if object implements one of multiple interfaces. Also we such code leads to runtime error, but it’s an only single way to make overloaded functions and methods.
+f1 +f2 +f3 !f1(undefined) +f2 TypeError: x3.f3 is not a function at testObject (/home/kol/WebstormProjects/ts-test/src/main.ts:227:6) at Object. (/home/kol/WebstormProjects/ts-test/src/main.ts:234:1) at Module._compile (module.js:569:30) at loader (/usr/lib/node_modules/ts-node/src/index.ts:288:14) at Object.require.extensions.(anonymous function) [as .ts] (/usr/lib/node_modules/ts-node/src/index.ts:305:14) at Module.load (module.js:503:32) at tryModuleLoad (module.js:466:12) at Function.Module._load (module.js:458:3) at Function.Module.runMain (module.js:605:10) at Object. (/usr/lib/node_modules/ts-node/src/_bin.ts:179:12)
The text was updated successfully, but these errors were encountered:
Typescript check interface implementation
Last updated: Jan 23, 2023
Reading time · 3 min
# Check if an Object implements an interface in TypeScript
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.
Copied!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.
The 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.
Copied!function isAnEmployee(obj: any): obj is Employee return 'id' in obj && 'name' in obj && 'salary' in obj; >
Depending on your use case, you might need to be more strict and check not only for the existence of the properties but also for the types of the values.
This can get pretty verbose if your interface has many properties.
# Check if an Object implements an interface by using a type property
An alternative approach is to add a type property to the interface, for which you check instead.
Copied!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.
All we have to do in the function is check if the passed-in object has a type property that’s equal to Employee .
# Working around interfaces that extend from other interfaces
However, note that this can get difficult to manage if you have interfaces that extend from Employee .
Copied!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.
Want to learn more about working with interfaces in TypeScript ? Check out these resources: How to set up TypeScript interface Default values ,Declaring getters/setters in Interfaces and Classes in TS.
# A user-defined type guard is a better solution
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.
Copied!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 .
If you need to get an object’s key by value, click on the following link.
I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
How to check if an object implements an interface in Typescript
When using Typescript the compiler detects the type error. It supports any data type but it is not recommended to use because it tells the compiler that it doesn’t have to check the data type of the object. When the object isn’t the defined data type it crashes at runtime. It’s better to define concrete data type as much as possible. If it’s not possible to define concrete possible data types in the function you should use unknown data type instead of any .
You can clone my git repository if you want to run it on your pc.
Check the object data type by Type Guards
“in” Type Guards
Type Guard can be used when the object is Object type but if it is unknown data type it might not be an object. Therefore, the compiler says object is of type ‘unknown’ . In this case, you need to cast it to any.
const personData: unknown = < name: "yuto", age: 30, >; if ("name" in (personData as any))
However, it doesn’t make sense to use unknown in this case and eslint shows a warning when any is used. Another way that we may come up with is a combination of instanceof and in keyword but it doesn’t work because the compiler still doesn’t know what data type personData is and it shows an error.
if (personData instanceof Object && "name" in personData) < // Property 'name' does not exist on type 'never' console.log(personData.name); >
instanceof interface
I know you tried to use instanceof for interface but the compiler shows an error like this below.
if(personData instanceof Person) <> // 'Person' only refers to a type, but is being used as a value here.ts(2693)
As the error message says interface is a type. instanceof requires an instance but an interface is just a definition.
User defined Type Guards
User-defined type guards can solve the problem.
export function isPerson(object: unknown): object is Person < return Object.prototype.hasOwnProperty.call(object, "name") && Object.prototype.hasOwnProperty.call(object, "age"); >if (isPerson(data)) < console.log(`name: $, age $`); >
The point here is to call hasOwnProperty function via call function because we don’t know whether the object argument is object type or not.
Failed trial by Abstract class
The following code is another trial but it didn’t work because data is just an object. It’s not a class.
export abstract class ManyArgs < public arg1: string = ""; public arg2: string = ""; public arg3: string = ""; >console.log("---- Abstract ----") const data = < args1: "str 1", args2: "str 2", args3: "str 3", >; if (data instanceof ManyArgs) < console.log(`$, $, $`) > else < console.log("instanceof doesn't work.") >// result // ---- Abstract ---- // instanceof doesn't work.
I tried to create a generic function for the check but it was impossible to create it because I couldn’t find a way to get a property list from interface. Object.keys() function requires object data type and we cannot pass interface there.
Check if the variable is NodeJS.ErrnoException type
Let’s check an actual example. fs.promises.readFile function throws NodeJS.ErrnoException . The data type of error variable used in catch is unknown from TypeScript version 4.4. The source is here. We can turn off useUnknownInCatchVariables or add as any to cast it but let’s try to use a user-defined type guard here.
The type guard function looks like this. There are other properties available but I think these two are enough.
export function isErrnoException(object: unknown): object is NodeJS.ErrnoException
Let’s try to read a file. If the file doesn’t exist, it throws an error. Without this type check, the compiler shows errors because those properties don’t exist on type “unknown”.
async function runExample() < try < await fs.promises.readFile("/not-exist-file"); >catch (e) < if (isErrnoException(e)) < console.log(`e.code: $`); console.log(`e.errno: $`); console.log(`e.message: $`); console.log(`e.name: $`); console.log(`e.path: $`); console.log(`e.stack: $`); console.log(`e.syscall: $`); > else < console.log(e); >> > runExample() .then(() => console.log("done")) .catch(() => console.log("error")); // e.code: ENOENT // e.errno: -4058 // e.message: ENOENT: no such file or directory, open 'C:\not-exist-file' // e.name: Error // e.path: C:\not-exist-file // e.stack: Error: ENOENT: no such file or directory, open 'C:\not-exist-file' // e.syscall: open
Strict Object Type Check
The way above checks only the property’s existence. If two interfaces have the same properties but one of the data types is different, we somehow need to differentiate them. In this case, typeof needs to be used to check the data type. However, if the data type is unknown type, we can’t access the property. The following post shows how to solve the problem.
I wrote the following article before.When we want to access a property of an object, we must somehow tell the compiler i.