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:
Lesson Two: Address Contemporary Issues
September 14, 2022
Comics are more interesting when they relate to real-world events