Type Casting with TypeScript: A tutorial

Type Casting with TypeScript: A tutorial

·

10 min read

The article was originally written on the DeadSimpleChat blog: Type Casting with TypeScript: A tutorial

Type Casting is an important technique and technology, especially in TypeScript.

TypeScript is a SuperSet of JavaScript that has strong type safety and potential for catching errors during compiling rather than when the program is run.

Here are some of the reasons why Type Casting is important in TypeScript.

  1. Working with Complex Types

  2. Working with legacy JavaScript Libraries

  3. Working with unknown types.

  4. Ability to do easy type manipulation

  5. Creating Type Guard functions

1. Working with Complex Types

To understand the intended data type, it is important to TypeCast one type into another. This is true when working with nested data types or complex data types

This way the developer can say what type they are expecting to the compiler. When working with complex data types and deeply nested data declaring the data types like this helps the compiler compile faster and increases productivity.

2. Working with legacy Javascript Libraries

If you want to ensure type safety and prevent run time errors in legacy JavaScript libraries that were not written in TypeScript

You can do this by TypeCasting expected types and ensure safety of your code.

3. Working with unknown types

When parsing JSON data or working with an external API, you might encounter unknown types.

With Type Casting you can specify the expected type of value, thus making it easy to maintain and performant code.

4. Ability to do easy type manipulation

With technologies like conditional types and mapped types you can easily and efficiently do TypeCasting reducing redundancy and maintaining type safety thus making the code more expressive

5. Creating Type Guard Functions

With TypeScript you can create custom Type Guard functions with the isType operator. Type casting helps assert if a given value is of a type that we are expecting it to be. Then If it is of the type we want it to be we can use the value in the next process or if the assert fails then we can think of what to do with the value.

Basics of Type Casting

Explicit Vs Implicit Type Casting

What is Implicit Type Casting?

TypeScript might sometimes convert a value from one type to another, this usually happens in an assignment to a function call.

The TypeScripts type checker automatically determines the inferred type of the given value and there is no manual intervention by the developer

Let us consider an example

let stringValue: string = '42';
let numberValue: number = stringValue as unknown as number; // Double Casting is done here causing implicit conversion

What is Explicit Type Casting?

Explicit Type casting is when the developer performs the type conversion intentionally, thus the developer explicitly provides the type

TypeScript offers 2 ways a developer might do this

  • Using the Angle Braclets

  • Using the as Keyword

Angle Brackets and the as Keyword

To cast a value to another type in TypeScript you need to place the type to cast in Angle Brackets followed by the value to cast

let us look at an example

let someValue: any = 'Some String Value';
let strLength: number = (<string>someValue).length;

This method is an older way to type cast in TypeScript. Let us look at another modern may to Type Cast and that is

Using the as Keyword

the as a keyword was added in typescript as a way to Type Cast. This method is easy to use and more readable as well.

The Older Angle Brackets method could also conflict with JSX and hence the as keyword is the preferred way of Type Casting in TypeScript

Let us consider an example

let someValue: any = 'This is a random string';
let strLength: number = (someValue as string).length;

No, Matter which method you use to typecast the TypeScript will ensure that the cast is valid and permitted.

TypeScript will always look to maintain Type safety.

Using Type Casting incorrectly can cause errors.

Examples

A Custom Type Guard

What are Type Guards?

Type Guards are functions. These functions narrow the scope of the given value to a perticular type

This enables the typescript to differentiate between types.

Creating custom type guards allows the developer to perform custom checks on the value and return a boolean type determining as to what the expected value of the type is

let us look at an example

class Individual {
  constructor(public name: string, public age: number) {}
}

class Car {
  constructor(public make: string, public model: string) {}
}

function isIndividual(obj: any): obj is Individual {
  return obj instanceof Individual;
}

const someItem = new Individual('Jo', 23);

if (isIndividual(someItem)) {
  // TypeScript will recognw someItem as Individual within this scope
  console.log(someItem.name);
} else {
  console.log('This Object is not an Individual object');

Casting in Functional Programming

What is functional Programming?

Functional Programming means having functional principles such as immutability, higher-order functions etc

To write maintainable and predictable code. Type Casting can also be done with functional programming methodology in typescript

Let us do Type Casting with map function

type Circle = {
  kind: 'circle';
  radius: number;
};

type Square = {
  kind: 'square';
  sideLength: number;
};

type Shape = Circle | Square;

const shapes: Shape[] = [
  { kind: 'circle', radius: 5 },
  { kind: 'square', sideLength: 10 },
];

// Writing a function to calculate a shape's area depending in the what kind it is

function area(shape: Shape): number {
  if (shape.kind === 'circle') {
    return Math.PI * shape.radius ** 2;
  } else {
    return shape.sideLength ** 2;
  }
}

const areas: number[] = shapes.map(shape => area(shape));

We are using conditional and mapped types to perform advanced Type Casting, We are also ensuring type safe while performing advanced type casting

Let us learn some more about the advanced techniques and technologies in the next section

Advanced techniques

There are 2 techniques that we are going to learn today.

1. Reflection and runtime type metadata.

2. using conditional and mapped types

Both these types are explained in detail below with examples

Reflection and runtime metadata

TypeScritpt be default provides type checking at compile time. If you need more dynamic type checking during run time you can use decorators and libraries such as reflect-metadata Reflection involves checking the data stucture of the type at runtime

let us consider an example using the reflect-matadata

import 'reflect-metadata';

class User {
  constructor(public name: string, public age: number) {}
}

function logType(target: Object, key: string) {
  const targetType = Reflect.getMetadata('design:type', target, key);
  console.log(`${key} has type ${targetType.name}`);
}

class MyClass {
  @logType
  public user: User;
}

const instance = new MyClass();

Using Conditional Types and Mapped Types for advanced casting

Conditional types

defining types based on a particular condition is known as Conditional types in TypeScript

This article is brought to you by DeadSimpleChat, Chat API and SDK for your website and app.

The condition types have a particular syntac and that is

T extends U ? X : Y

This means if T extends you then the type is X otherwise it is Y.

type IsString<T> = T extends string ? 'true' : 'false';

type StringCheck = IsString<string>; // 'true'
type NumberCheck = IsString<number>; // 'false'

Mapped types

You can iterate over existing types and modify their properties as needed to create new types

For example: We can create different versions of the same type that is read-only

type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;

const person: ReadonlyPerson = {
  name: 'Alice',
  age: 30,
};

// The following code would throw a compile time error
// person.age = 31;

Using conditional and mapped types, we can do complex Type Casting with TypeScript

TypeScript empowers developers with tools such as reflection and runtime metadata, conditional and mapped types and ensures type safety throughout your. code

Examples: Real life Examples and use cases for better understanding

1. Safely Parsing JSON:

Using JavaScript's built-in JSON.parse function

In this example we will be using type casting to check the type of Object parse by the function JSON.parse

JavaScript's built in function JSON.parse can parse a JSON string into a JavaScript Object

However TypeScript does not know what is the type of object parsed by the JSON.parse function

We will be using Type Casting to provide the correct type information

const individualInformation = '{"name": "John Doe", "age": 23}';
const parsedJSON = JSON.parse(individualInformation);

Custom Type Casting function

Next, we will be creating a custom guard function to validate the structure of JSON that is parsed with JSON.parse function

interface Individual {
  name: string;
  age: number;
}

function isIndividual(obj: any): obj is Individual {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    typeof obj.name === 'string' &&
    typeof obj.age === 'number'
  );
}

Now, we have created a custom function that we can use to assert the type of JSON that was parsed by JSON.parse function

Asserting Type of the parsed JSON

Let us now assert that the JSON parsed by the JSON.parse Object is of a particular type or not

const IndividualInformation = '{"name": "Joe", "age": 23}';
const parsedJSON = JSON.parse(IndividualInformation);

if (isIndividual(parsedJSON)) {

  const individual: Individual = parsedJSON;
  console.log(`Hi, I am ${individual.name} and I am ${individual.age} years old.`);
} else {
  console.error('The JSON string does not represent a Individual object');
}

In this above example we are using a custom guard function isIndividual to check that the parsed JSON is of the type Individual

The Individual Object here is serving as an implicit type cast and maintains safty and code integrity

2. Transform API Responses

In this example, we will be transforming JSON API responses with a utility function and using type manipulation techniques to achieve type safety

As a sample api we will be using the JSON placeholder websites todos api

https://jsonplaceholder.typicode.com/

This endpoint returns the following data:

[
  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  },
 ...
]

Creating the function to fetch the data from the API

We are going to create a function that would fetch the data from the todos endpoint

async function fetchTodos() {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos');
  const todos = await response.json();
  return todos;
}

Creating a utility function to handle JSON responses

We know that it is not guaranteed that the JSON responses we recieve from the endpoint are not always going to be in the structure or format that we require them to be

We can create a utility function that checks and narrows the type of response using a Type check function

interface Todo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

function isTodoArray(obj: any): obj is Todo[] {
  return Array.isArray(obj) && obj.every(isTodo);
}

function isTodo(obj: any): obj is Todo {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    typeof obj.userId === 'number' &&
    typeof obj.id === 'number' &&
    typeof obj.title === 'string' &&
    typeof obj.completed === 'boolean'
  );
}

Ensure type safety with advanced type manipulation

We are going to handle the JSON responses using the utility function

async function main() {
  const fetchedTodos = await fetchTodos();

  if (isTodoArray(fetchedTodos)) {
    // typescript will recognise the 
    const todos: Todo[] = fetchedTodos;

    // Advanced type manipulation: Extract only completed todos
    const completedTodos = todos.filter(todo => todo.completed);

    console.log('Completed Todos:', completedTodos);
  } else {
    console.error('API response does not match expected structure');
  }
}

main().catch(error => {
  console.error('An error occurred:', error);
});

In the above example we are using the fetched data from the JSON placeholder website and using a utility function to check the JSON response and assert the type of JSON using a function

Potential Pitfalls and Common Mistakes

Need Chat API for your website or app

DeadSimpleChat is an Chat API provider

  • Add Scalable Chat to your app in minutes

  • 10 Million Online Concurrent users

  • 99.999% Uptime

  • Moderation features

  • 1-1 Chat

  • Group Chat

  • Fully Customizable

  • Chat API and SDK

  • Pre-Built Chat

Conclusion

In this article, we learned about TypeCasting in TypeScript

We learned about the importance of TypeCasting, and what are the basic principles of typecasting, real-world application and other things that are required in TypeCasting with TypeScript like safety and implicit and explicit type casting

We covered modern technologies and demonstrated real-life examples using JSON and API responses

I hope you liked the article and thank you for reading.