Assertions in TypeScript

tomoy

Tomohiro Yoshida

Posted on April 23, 2023

Assertions in TypeScript

In TypeScript, there is a technique that is so-called Assertions.
Generally, it is better to avoid using assertion because it forces the TypeScript type system to write its type over. However, there are some situations it is reasonable to use assertion to make your code work as you expect. In this article, I will share assertions and how to use assertions in your project.

Type Assertions

When you use certain methods, it may return something that you did not expect. Here is an example.

const originalArray = ["hello", "world"];
// const originalArray: string[]

const jsonString = JSON.stringify(originalArray);
// const jsonString: string

const parsedArray = JSON.parse(jsonString);
// const parsedArray: any

console.log(parsedArray);
// [LOG]: ["hello", "world"]
Enter fullscreen mode Exit fullscreen mode

In this example, the type of parsedArray becomes any though we can retrieve the data we want. This is a good use case of type assertion because the JSON.parse method always returns any.

(method) JSON.parse(text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined): any
Enter fullscreen mode Exit fullscreen mode

That is why, we will have to use assertion to notify the TypeScript type system of the actual type of the returned value. Here is an updated code.

const originalArray = ["hello", "world"];
// const originalArray: string[]

const jsonString = JSON.stringify(originalArray);
// const jsonString: string

const parsedArray = JSON.parse(jsonString) as string[];
// const parsedArray: any

console.log(parsedArray);
// [LOG]: ["hello", "world"]
Enter fullscreen mode Exit fullscreen mode

Perfect! You can avoid unexpected runtime errors by using assertions properly
Let me give you another example.

const element = document.getElementById('myElement');

console.log(element.innerText);
// type error: 'element' is possibly 'null'.
Enter fullscreen mode Exit fullscreen mode

In this above example, you will encounter the type error, "'element' is possibly 'null'.". That is because document.getElementById returns union type, HTMLElement | null. Therefore, it will be good to cast the HTMLElement type if you are 100% sure that it is not null.

const element = document.getElementById('myElement') as HTMLElement;
console.log(element.innerText); // OK
Enter fullscreen mode Exit fullscreen mode

Please note that type assertion is not the only solution in this scenario. You may be able to use type guard instead like below:

const element = document.getElementById('myElement');

if (element) {
  console.log(element.innerText); // OK
}
Enter fullscreen mode Exit fullscreen mode

Non-Null Assertions

Speaking of the document.getElementById method, you may be able to use Non-null assertion if it is enough to tell the TypeScript type checker that the type of the returned value is NOT null.
By adding a ! instead of as type, you can just notify the type checker that the returned value will never be null. Here is an example:

const element = document.getElementById('myElement')!;

console.log(element.innerText); // OK
Enter fullscreen mode Exit fullscreen mode

Const Assertions

Finally, let me introduce const assertions that work differently from other assertions.
When you use the const assertions, values affected by the const assertion will freeze.
For example, if a value is Array, it will be a readonly tuple that is an immutable array. If a value is Number or String, the type of the value becomes literal instead of the general primitive. If you use it for an object, the properties of the object will be readonly.
Here is an example:

// Readonly tuple (immutable array)
const coordinates = [1, 2, 3] as const;
// coordinates: readonly [1, 2, 3]

// The following line will cause a TypeScript error because
// the array is now a readonly tuple and cannot be modified.
// coordinates[0] = 5;

// Literal types
const age = 30 as const;
// age: 30

// The following line will cause a TypeScript error because
// age is now a literal type and cannot be assigned a new value.
// age = 31;

const student = "John" as const;
// student: "John"

// The following line will cause a TypeScript error because
// him is now a literal type and cannot be assigned a new value.
// student = "Jane";

// Readonly properties for objects
const person = {
  firstName: "Alice",
  lastName: "Smith",
} as const;
// person: { readonly firstName: "Alice"; readonly lastName: "Smith" }

// The following line will cause a TypeScript error because
// person.firstName is now a readonly property and cannot be modified.
// person.firstName = "Bob";

Enter fullscreen mode Exit fullscreen mode

In this example, I have used const assertions to create readonly tuples, literal types, and readonly properties for objects. Notice how trying to modify these values causes TypeScript errors, since const assertions make them immutable.

Conclusion

In conclusion, TypeScript assertions are a powerful tool for handling specific cases where type inference falls short or additional type information is required. They include type assertions, non-null assertions, and const assertions. While they can help prevent runtime errors and improve code readability, they must be used carefully. Using too many assertions can lead to potential runtime issues if used incorrectly. Always prioritize TypeScript's type inference and type-checking mechanisms, resorting to assertions only when necessary to ensure a robust and maintainable codebase.

💖 💪 🙅 🚩
tomoy
Tomohiro Yoshida

Posted on April 23, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related