Use the JavaScript “in” operator for automatic type inference in TypeScript

Rares Matei
InstructorRares Matei

Share this video with your friends

Send Tweet
Published 4 years ago
Updated 4 years ago

Sometimes we might want to make a function more generic by having it accept a union of different types as an argument. Using the JavaScript “in” operator, we can test for the presence of different properties on the argument object, and TypeScript will automatically infer the exact type of our object in that block. It does this by looking at all the possible types in the union and then keeping only the ones that have that specific property defined.

Instructor: [00:00] Let's say I have two interfaces, one for admin and another for a normal user. I have a function, redirect, that accepts an object as an argument that can be either an admin or a user.

[00:11] Now, if the user is an admin, I want to redirect to a specific route on my page and pass in this admin-only property. Otherwise, I want to use the normal route and pass in just the email, which is a user-only property.

[00:27] Now, what can we write in here to have it pass only if the user is an admin? We can ask TypeScript to assume that the user is an admin and then check if a specific admin-only property is defined on it -- in this case, the role.

[00:42] Even though we can tell visually that this line of code will only get executed if the user is an admin, TypeScript can't make that inference and it's throwing an error here because it still can't be sure that our object is an admin. It thinks it might still be a user, and users don't have a role property on them.

[01:01] I can also create a custom type guard right below here. If this returns true, then the user is an admin. That fix our problem.

[01:12] If I hover over user in this context, you can see TypeScript knows it's an admin. But that might become a bit too much if we start creating these functions every time we need TypeScript to infer types based on simple properties.

[01:27] There is, of course, a third option. The JavaScript in operator is useful for checking if certain properties exist on objects. In this case, all I want to check if the property role exists on my argument.

[01:41] Since TypeScript 2.7, the compiler will now start to infer the type of an object in a block that wraps a condition containing the in operator. In this case, TypeScript will essentially think, "OK, so my input can only be of type admin or user."

[02:00] If it passes that if condition, that means it has the role property on it. Out of admin and user, the only type that includes the role property is admin. That means in this block it must be admin. We can confirm that by hovering over it.

[02:16] This is great. All of the errors went away now, both in the first block and in the second else block. If I want to use user for something else here, you can see I only get suggestions for admin properties, which is ID and role.

[02:29] Also, because I marked my argument as either an admin or a user, and since we confirmed that if it goes in the if block it's an admin, that means that it's only going to go in the else block if it's a user.

[02:40] We can confirm this by trying to access some properties on it. We can see that I now only get user properties. I only get email. TypeScript will keep doing this intelligent inference in all of our logic branches.

[02:52] To recap, we've seen how TypeScript can confer the type of an object based on conditions like this. While custom type guards work great for that purpose, they might add unnecessary indirection to our code, so we can use the JavaScript in operator to both check if a property exists on an object and to hint to the TypeScript compiler about the type of that object.

jay shah
jay shah
~ 4 years ago

The javascript "in" operator looks all the way into the prototype chain, so what type does it know to assume then?

Jean-Denis Vauguet
Jean-Denis Vauguet
~ 4 years ago

TypeScript provides static typing. The "in" operator is a runtime construct. Basically "in" has nothing to do with TypeScript's types/interfaces. It merely checks whether, at runtime, some object has some property, all the way into that object's prototype chain indeed. So "in" does not assume anything/any type. That's a known limitation to the TypeScript model, that is: it compiles to JavaScript, so typing information is lost and it's up to the programmer mimicking the typing logic through properties check and whatnot.

If you're looking for runtime type checking with TypeScript, you might try https://github.com/fabiandev/ts-runtime but it's not officially supported by any means.

JP Lew
JP Lew
~ 3 years ago

What's the difference between writing if("role" in usr) and if(usr.role)?

Steeve
Steeve
~ 3 years ago

Wow this is horrible. How is there not a built-in way to check for a specific type?

The day someone adds a role attribute to User people will be redirected to the Admin route?

Rares Matei
Rares Mateiinstructor
~ 3 years ago

Wow this is horrible. How is there not a built-in way to check for a specific type?

The day someone adds a role attribute to User people will be redirected to the Admin route?

That's right! It's not at all safe/secure in this case! You'll usually want to use properties that you know will be part of a specific type - in the above example "role" is a property which might exist on a non-admin user as well, so it's not the best example.

But once you're sure a specific property will be part of just a single interface, this is one of the easiest ways to get automatic type inference. As interfaces don't exist in vanilla JS, there's no way to check whether your object is of a specific interface, as that would stop working at runtime - so you need to use the "in" operator as something both JS and TS can understand.

You can definitely check if something is of a specific class though using "typeof"

Rares Matei
Rares Mateiinstructor
~ 3 years ago

What's the difference between writing if("role" in usr) and if(usr.role)?

if(usr.role) will not work in the lesson code. Typescript will complain that the "role" attribute does not exist on something that can be Admin | User. if(usr.role) is assumed to check whether the value of usr.role is defined, and not whether the property actually exists.

So to make TS narrow down to the correct interface for us, we need to if('role' in usr) which simply checks if that property exists on the object (take care that a property can exist even if it's undefined).

Joël
Joël
~ 3 years ago

What's the difference between writing if("role" in usr) and if(usr.role)?

Just for information in JS if you do something like if(usr.role) you are doing implicit cast (coertion). You are NOT checking if property is in the object.

~ a year ago

using custom function type guard is much better than in, especially in this example. It is more readable - you want to allow action if object is an Admin, not if it has particular property. Second reason - imagine your data structure for Admin changes, for example property name has changed. Using function you do a change in one place, using in you need to scan whole codebase to change it.