Assertions in TypeScript
Tags: typescript, javascript, programming
Published: 2023-04-23 22:56:43

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.
```ts
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"]
```
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`.
```ts
(method) JSON.parse(text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined): any
```
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.
```ts
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"]
```
Perfect! You can avoid unexpected runtime errors by using assertions properly
Let me give you another example.
```ts
const element = document.getElementById('myElement');

console.log(element.innerText);
// type error: 'element' is possibly 'null'.
```
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`.
```ts
const element = document.getElementById('myElement') as HTMLElement;
console.log(element.innerText); // OK
```
Please note that type assertion is not the only solution in this scenario. You may be able to use **type guard** instead like below:
```ts
const element = document.getElementById('myElement');

if (element) {
  console.log(element.innerText); // OK
}
```
## 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:
```ts
const element = document.getElementById('myElement')!;

console.log(element.innerText); // OK
```
## 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:
```ts
// 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";

```
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.