Type Casting is an important technique and technology, especially in TypeScript.
TypeScript is a SuperSet of JavaScript that has strong typesafety and potential for catching errors during compiling rather then 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 maipulation
- 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
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 prefferred 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 fo 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 consitional 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 perticular 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
Which 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:
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
1. Over Reliance on any
type
You should use the any
type strictly where it is nessesory to use, otherwise it basically defeats the purpose of strong typecasting in typescript
The any
type bypasses the typescript compile time checks and can lead to potential runtime errors that the compiler cannot detect
2. Incorrect or overuse of Type Assertions
Overusing typer assertions can lead to code that compiles well but fails to run at runtime because the assertions were incorrect
Always use type gaurds to check to perform runtime checks before casting the types
3. Misunderstanding Union and Intersection Types
Many developers that are new to typescript confuse union (A|B) and intersection (A&B) types
Union types allow any type that is within the union set as contract to intersection types that only allow combine types that meet properties rach type
4. Ignoring Compiler Warnings
Always consider typescrpt compiler warnings, they are there to help you out.
Ignoring the compiler warnings can lead code that is not type safe, potentially resulting in runtime errors
5. Improper Use of Null and Undefined
if you do not properly initialize a variable or check for null
or undefined
values before using them, could lead to Could not read the property of undefined error
Always use strict null checks that are enabled in TypeScript configurations and utilizing optional chaining ?
operations to handle cases
6. Misusing Type Inference
The TypeScript is good at infering types but imporper use of this features could lead to unreadable code or unexpected type inference.
If you are in doubt, always explicitly declare types for certain variables and function return values
7. Failing Update Types with Business logic
As the code base evolves the underlying data structures and types may need to be updated
Implement a robust process for updating types as a part of regular code maintenance.
Always think types as living code base and update them when maintaining code, so that they may refect the current state of affairs
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 imporatance 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.