last updated: October 23, 2023
3 minute read
A Cheat Sheet For Using Type Assertions in Typescript
Maybe you've heard that Typescript uses duck typing, a system where two types are considered
equivalent if they simply share the same structure. While duck typing is intuitive when it comes to
providing a variable with a type annotation (i.e. using a : in const user: User), some of the
finer rules around casting with the as keyword can be a bit trickier to remember.
see the following typescript playground for live examples of everything covered in this article!
For example, say we have the following type User, which we'll refer to throughout the article:
interface User {email: string;id: number;}
In order to annotate a variable with the type User, it must contain all the required properties,
with all the expected types.
// this worksconst user: User = { email: "user@site.com", id: 123 };// these all error!const userWithPropertyOfWrongType: User = { email: "user@site.com", id: "123" };const userWithExtraProperty: User = {email: "user@site.com",id: 123,isLoggedIn: true,};const userWithMissingProperty: User = { email: "user@site.com" };const userWithExtraAndMissingProperty: User = {email: "user@site.com",isLoggedIn: true,};
So far so good - direct type annotations are the strictest form of typesafety with little room for
ambiguitity. Type asssertions, on the other hand (using the as keyword), provide a little more
wiggle room.
// Anytime you can use a type annotation, you can also use a type assertionconst user = { email: "user@site.com", id: 123 } as User;// Extra properties are alrightconst userWithExtraProperty = {email: "user@site.com",id: 123,isLoggedIn: true,} as User;// Missing properties are fine tooconst userWithMissingProperty = { email: "user@site.com" } as User;// Missing _and_ extra properties will fail to compile!const userWithExtraAndMissingProperty = {email: "user@site.com",isLoggedIn: true,} as User;// Incorrect types won't work eitherconst userWithPropertyOfWrongType = {email: "user@site.com",id: "123",} as User;
any and unknown
There is a catch, however, and that has to do with the any and unknown types in Typescript.
Interestingly enough, any type can be cast as any or unknown, and unknown and any can be
cast as any type!
const typedAsAny: any = "any";const typedAsUnknown: unknown = "unknown";// can cast any or unknown as any typetypedAsAny as User;typedAsUnknown as User;// ... or anything as any or unknown!const user: User = { email: "user@site.com", id: 123 };user as any;user as unknown;
In practical terms, this means if you want to assert a variable as a given type, but you're unable to for one of the reasons discussed above, you can use chained type assertions to override the type checker:
const userAsAny = { email: "user@site.com", isLoggedIn: true } as any;const userAsaUser = userAsAny as User;// or in one lineconst user = { email: "user@site.com", isLoggedIn: true } as any as User;
The example above also applies for unknown
Bonus: Primitive types
There are seven primitive types in Javascript - string , number , BigInt , boolean ,
Symbol , null and undefined - and none of them can be cast to one another. Easy to remember!
However, when the strictNullChecks option is disabled in your tsconfig, null and undefined
can instead be cast as any type, and any type can be cast as null or undefined - including
complex types like objects!
That's it - a short article, but hopefully helpful in clearing a bit of a tricky area in the distinction between type annotations and type assertions. Thanks for reading!
you might also like:
Data Fetching in React: Parent-Agnostic vs Parent-Dependent Children
July 27, 2022
Null checking: the responsibility of the parent or the child?