Recursive Type Guards In TypeScript
Last Updated :
25 Sep, 2024
In TypeScript type guards help determine the type of a variable at runtime, they are especially useful when dealing with complex types like unions, discriminated unions or even recursive structures and a recursive type guard is a type guard function that can handle complex nested types, including those that are self-referential.
Prerequisite
What are Recursive Type Guards?
Recursive type guards are functions that are designed to check types for nested or self-referential structures at runtime, these structures might contain recursive relationships such as trees or linked lists where a type references itself.
Why use Recursive Type Guards?
The static type system of TypeScript is strong but sometimes insufficient when dealing with recursive structures and in these cases a recursive type guard helps ensure that each element in a complex structure is correctly typed, even deep into the recursion, this can be useful in data validation or enforcing constraints on complex objects, ensuring runtime safety.
Handling Recursive Types
Recursive types, as mentioned earlier, are types that reference themselves. In TypeScript, you can effectively manage recursive types with various constructs and techniques.
Type Guards
Type Guards are used to narrow down the type of a variable within a conditional block. They help TypeScript understand what type of a variable is at a specific point in code.
Example:
type IsArray<T> = T extends Array<any> ? true : false;
Mapped Types
Mapped types give you the possibility of generating new types based on the characteristics of other ones, this feature is useful for the recursive types, as they allow for defining manipulations at every depth of recursion.
Example:
type ReadonlyTreeNode<T> = {
readonly [K in keyof T]: ReadonlyTreeNode<T[K]>;
};
Utility Types
Base utility types such as Partial, Required, Record, and so on, can possibly work with the recursive structures to impose limitations or establish new forms, to illustrate, Partial<T>can specify the properties of recursive structure that can be omitted.
Example:
type PartialTreeNode = Partial<TreeNode>;
Conditional Types
Conditional sorts can be used to create kind relationships that rely upon the form of the statistics, they are especially beneficial in recursive scenarios to differentiate between types based totally on homes.
Example:
type IsTreeNode<T> = T extends TreeNode ? true : false;
Recursive Type Guard for a Tree Structure
We want to create a recursive type guard, in order to guarantee that given object complies to TreeNode type. The isTreeNode function first tries to verify that the node is an object that contains a number as value and then it tests whether children property (if it exists) is an array and then goes over child array calling the function isTreeNode again to every child element.
Exanple: This example shows the Recursive Type Guard for a Tree Structure.
JavaScript
/* index.ts */
interface TreeNode {
value: number;
children?: TreeNode[];
// Recursive reference to the same type
}
function isTreeNode(node: any): node is TreeNode {
if (typeof node !== 'object' || node === null) {
return false;
}
if (typeof node.value !== 'number') {
return false;
}
if (node.children) {
if (!Array.isArray(node.children)) {
return false;
}
// Recursively check each child
for (const child of node.children) {
if (!isTreeNode(child)) {
return false;
}
}
}
return true;
}
// Example Usage
const tree = {
value: 10,
children: [
{ value: 5 },
{ value: 15, children: [{ value: 12 }, { value: 18 }] }
]
};
console.log(isTreeNode(tree));
// Output: true
Output:
true
Recursive Type Guard for JSON-like Object
Lets consider an example here we will implement a recursive type guard for verification if an object follows structure of a JSON object, which can be a string, number, boolean, null, array, or another object. The isJSONValue function is implemented in order to unify all possible usages of a value and check that it corresponds to one of the possible JSON values and in case the value is an array or object, the function traverses each element or each key-value pair to notice whether they coded in JSONValue type.
Exanple: This example shows the Recursive Type Guard for JSON-like Object.
JavaScript
/* index.ts */
type JSONValue = string | number | boolean | null | JSONObject | JSONArray;
interface JSONObject {
[key: string]: JSONValue;
}
interface JSONArray extends Array<JSONValue> { }
function isJSONValue(value: any): value is JSONValue {
if (value === null) return true;
if (typeof value === 'string' || typeof value === 'number'
|| typeof value === 'boolean') {
return true;
}
if (Array.isArray(value)) {
// Recursively check each item in the array
return value.every(isJSONValue);
}
if (typeof value === 'object') {
// Recursively check each key-value pair in the object
for (const key in value) {
if (value.hasOwnProperty(key) && !isJSONValue(value[key])) {
return false;
}
}
return true;
}
return false;
}
// Example Usage
const jsonObject = {
name: "Pankaj",
age: 20,
married: false,
children: [
{ name: "Vishal", age: 5 },
{ name: "Vinit", age: 8 }
]
};
console.log(isJSONValue(jsonObject));
// Output: true
Output:
true
Recursive Type Guard for a Linked List
Linked list is another recursive data-structure which can be validated using recursive type guards. The method called isListNode determines whether a given node is a ListNode and it then goes on to do same for the next node in a recursive manner, in order to confirm to whether whole link is of ListNode typed structure.
Exanple: This example shows the Recursive Type Guard for a Linked List.
JavaScript
/* index.ts */
interface ListNode {
value: number;
next?: ListNode;
// Recursive reference to the next node
}
function isListNode(node: any): node is ListNode {
if (typeof node !== 'object' || node === null) {
return false;
}
if (typeof node.value !== 'number') {
return false;
}
if (node.next !== undefined && node.next !== null) {
return isListNode(node.next);
// Recursive check
}
return true;
}
// Example Usage
const linkedList = {
value: 1,
next: {
value: 2,
next: {
value: 3
}
}
};
console.log(isListNode(linkedList));
// Output: true
Output:
true
Conclusion
Recursive type guards are just well suited for runtime validation of complex and self-referential structures, using recursion and TypeScript's type guards, you should make sure that your data corresponds to the specified structure which in turn increases reliability and reduces possible mistakes on active code, the last illustrative examples covered trees, JSON objects and linked lists are in fact very useful for imaging and subsequent implementation of recursive type guards. This checks if a type is a TreeNode
at each recursive level.
Similar Reads
TypeScript Narrowing typeof type guards
In this article, we are going to learn about Narrowing typeof type guards. TypeScript is a popular programming language used for building scalable and robust applications. In TypeScript, the typeof type guard allows you to narrow down a variable's type based on the result of the typeof operator. Thi
3 min read
Higher-Order Types in TypeScript
Higher-order types are among the advanced aspects of Typescript that give priority to types as first-class citizens, similar to higher-order functions of JavaScript that accept a function as an argument or return a function, higher-order types can accept types or return types. These are the followin
6 min read
What are Recursive Types & Interfaces in TypeScript ?
In TypeScript, recursive types and interfaces are constructs that reference themselves within their definitions, enabling the modeling of complex, nested data structures. They are particularly useful for representing hierarchical data such as trees and linked lists.By allowing types to be defined in
4 min read
How to use Type Guards in TypeScript ?
Here are the methods to use type guards in TypeScript: 1. Using typeof Type GuardsThe typeof operator checks the type of a variable, primarily for primitive types like string, number, boolean, etc. [GFGTABS] JavaScript function processValue(value: string | number) { if (typeof value === 'string
3 min read
Typescript Generic Type Array
A generic type array is an array that is defined using the generic type in TypeScript. A generic type can be defined between the angled brackets(). Syntax:// Syntax for generic type arraylet myArray: Array;Example 1: Creating a simple generic type array of number type in Typescript. [GFGTABS] JavaSc
1 min read
TypeScript - Recursive Types and Utility Types
TypeScript adds strong typing to JavaScript. Recursive Types define types that refer to themselves, useful for trees or nested objects. Utility Types simplify type changes, like making properties optional or read-only. These tools help write clear and flexible code. Recursive Types in TypeScriptA re
4 min read
TypeScript Generic Types
TypeScript Generic Types can be used by programmers when they need to create reusable components because they are used to create components that work with various data types and this provides type safety. The reusable components can be classes, functions, and interfaces. TypeScript generics can be u
2 min read
TypeScript Truthiness Narrowing Type
In this article, we are going to learn about Truthiness narrowing Type in Typescript. TypeScript is a popular programming language used for building scalable and robust applications. In TypeScript, truthiness narrowing is a concept that allows you to narrow down the type of a variable based on its t
3 min read
What is Recursive Generic in TypeScript ?
In TypeScript, a recursive generic is a type that refers to itself within its definition, enabling the creation of data structures or types containing references to the same type within their structure. This capability proves invaluable when dealing with nested or hierarchical data structures like t
4 min read
TypeScript Return Type Annotations
TypeScript Return Type Annotations allow you to define the expected return type of a function, enhancing code clarity and type safety. By specifying the return type (functionName(): ReturnType), you ensure the function returns the correct type, catching mismatches and reducing potential errors durin
2 min read