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 works
const 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 assertion
const user = { email: "user@site.com", id: 123 } as User;
// Extra properties are alright
const userWithExtraProperty = {
email: "user@site.com",
id: 123,
isLoggedIn: true,
} as User;
// Missing properties are fine too
const 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 either
const 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 type
typedAsAny 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 line
const 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

comics
reading
rant
© elan medoff