TypeScript Recursive Types
A recursive type is a type that refers to itself inside its own definition. The reference passes through a container, like an array or an object, so values can nest to any depth.
A JSON value type that names itself
The union lists the plain JSON kinds first. The last two branches hold JsonValue again, once in an array and once in a record.
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
const profile: JsonValue = { userName: 'Ada', scores: [10, 20], active: true, note: null };
console.log(JSON.stringify(profile));A tree node type
Each node holds a label and an array of child nodes. The leaf nodes carry an empty children array, and that empty array is where the recursion stops.
type TreeNode = { label: string; children: TreeNode[] };
const tree: TreeNode = {
label: 'root',
children: [
{ label: 'left', children: [] },
{ label: 'right', children: [{ label: 'leaf', children: [] }] },
],
};
function countNodes(node: TreeNode): number {
let total = 1;
for (const child of node.children) {
total += countNodes(child);
}
return total;
}
console.log(countNodes(tree));
console.log(tree.children[1].children[0].label);A directly circular alias is refused
This alias names itself with no container in between. The compiler refuses it, because a value of this type could never be built.
type Chain = Chain | null;Example
type Folder = { folderName: string; files: string[]; folders: Folder[] };
function countFiles(folder: Folder): number {
let total = folder.files.length;
for (const sub of folder.folders) {
total += countFiles(sub);
}
return total;
}
const rootFolder: Folder = {
folderName: 'docs',
files: ['readme.md'],
folders: [
{ folderName: 'guides', files: ['start.md', 'faq.md'], folders: [] },
{ folderName: 'notes', files: [], folders: [{ folderName: 'old', files: ['draft.md'], folders: [] }] },
],
};
console.log(countFiles(rootFolder));
console.log(rootFolder.folders[1].folders[0].folderName);Key points
- A JSON value type names itself in the array and record branches of its union.
- A tree node type holds child nodes of the same type in an array member.
- Reading a recursive value follows the same nesting the type describes.
- A directly circular alias is refused, because it never passes through a container.
In this example, the Folder type names itself in the folders member, so the tree of folders can nest to any depth. The countFiles function mirrors that shape, adding the files of each folder and recursing into every subfolder.
Note
The recursion in a type ends where the value ends, with an empty array or a plain leaf value.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท TypeScript Playground
Exercise
Why does the compiler accept JsonValue but refuse type Chain = Chain | null?