Table of contents
- 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
- Basics of Type Casting
- Examples
- A Custom Type Guard
- Advanced techniques
- Using Conditional Types and Mapped Types for advanced casting
- Examples: Real life Examples and use cases for better understanding
- 1. Safely Parsing JSON:
- Potential Pitfalls and Common Mistakes
- Need Chat API for your website or app
- Conclusion
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.
Working with Complex Types
Working with legacy JavaScript Libraries
Working with unknown types.
Ability to do easy type manipulation
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.