カテゴリー
Typescriptでファイルシステムのフォルダ構造を型で表現する
※ 当ページには【広告/PR】を含む場合があります。
2025/07/15

Typescriptの型プログラミングの応用的な利用例として、ファイルシステムのフォルダ構造を型で表現するテクニックについて考えてみましょう。
以前の記事では、GraphQLのスキーマ型をTypescriptの型でどう扱うかを紹介しました。
今回はより身近な例として、一般的なファイルシステムのフォルダ構造をTypescriptの型で表現することに挑戦します。 Typescriptの実践的な型プログラミングの利用例として、その強力さを感じていただければ幸いです。
フォルダ構造について
OSを問わずファイルシステムには固有のフォルダ構造が存在しています。 今回は一般的なLinuxOSのファイルシステムを念頭に置いて考えていきます。
Linuxでのフォルダ構造を表示してくれるコマンドとしては、
tree
tree
.
├── dir1
│ ├── fileA.txt
│ └── dir2
│ └── fileB.txt
├── fileC.txt
└── dir3
└── fileD.txt
3 directories, 4 files
フォルダ構造には所有権やシンボリックリンクなど、裏で様々なプロパティが設定されていますが、今回は話を単純化するため、以下のような静的プロパティを部分型にもつことを想定します。
type FileOrFolder = {
__type: 'file' | 'folder',
createdAt: Date,
updatedAt: Date,
}
フォルダ構造の型を設計する
当然、
FileOrFolder[]
フォルダ構造を型として表現するためには、回帰的な型表現と、Mapped型を利用します。
実装例は以下のようなものとなります。
//ファイル・フォルダが共通してもつプロパティ
type FileOrEmptyFolder = {
__type: 'file' | 'folder',
createdAt: Date,
updatedAt: Date,
};
//(空でない)フォルダを表現した構造
type Folder = FileOrEmptyFolder & {
[K in Exclude<string, keyof FileOrEmptyFolder>]: FileOrEmptyFolder | Folder
};
//フォルダ構造
type FileOrFolder = FileOrEmptyFolder | Folder;
//フォルダ構造をテスト ... フォルダを表したFileOrFolder型のインスタンスの例
const fof: FileOrFolder = {
__type: 'folder',
createdAt: new Date(),
updatedAt: new Date(),
root: {
__type: 'folder',
createdAt: new Date(),
updatedAt: new Date(),
'a': {
__type: 'folder',
createdAt: new Date(),
updatedAt: new Date(),
'0': {
__type: 'file',
createdAt: new Date(),
updatedAt: new Date(),
}
},
'b.txt': {
__type: 'file',
createdAt: new Date(),
updatedAt: new Date(),
},
c: {
__type: 'folder',
createdAt: new Date(),
updatedAt: new Date(),
"e.txt": {
__type: 'file',
createdAt: new Date(),
updatedAt: new Date(),
}
}
}
};
このコードでは、まず
FileOrEmptyFolder
__type
'file'
'folder'
createdAt
updatedAt
次に、
Folder
FileOrEmptyFolder
ここで重要なのが
[K in Exclude<string, keyof FileOrEmptyFolder>]: FileOrEmptyFolder | Folder
これはMapped型を利用しており、
FileOrEmptyFolder
__type
createdAt
updatedAt
K
FileOrEmptyFolder
Folder
これにより、フォルダが再帰的に子フォルダやファイルを持つ構造を表現できます。
そして、
FileOrFolder
FileOrEmptyFolder
Folder
最後に確認として、
fof
root
a
b.txt
c
a
0
c
e.txt
ユーティリティメソッドの実装例
では先程の型の定義を利用したメソッドを作ってみましょう。
以下のメソッドは、先程作ったフォルダ構造の型のインスタンスからフォルダかファイルのパスを一覧として表示させるものです。
function listDirOrFile(input: FileOrFolder | undefined, parent: string[] = ['']) {
if (input == null) return;
let result: FileOrFolder | null = null;
for (const key in input) {
if (['__type', 'createdAt', 'updatedAt'].includes(key)) {
continue;
}
console.log('PATH:', [...parent, key].join("/"));
const _child = (input as Folder)[key];
if (_child) {
listDirOrFile(_child, [...parent, key]);
}
}
return result;
}
listDirOrFile(fof);
実行すると、
"PATH:", "/root"
"PATH:", "/root/a"
"PATH:", "/root/a/0"
"PATH:", "/root/b.txt"
"PATH:", "/root/c"
"PATH:", "/root/c/e.txt"
となり回帰的なフォルダ構造のパスが一覧として表示されているのが分かります。
逆にフォルダ構造からパスを指定して、該当するフォルダかファイルを返すメソッドは以下のようになります。
function findDirOrFile(path: string, input: FileOrFolder): FileOrFolder | null {
if (input == null) return null;
let result: FileOrFolder = input;
for (const _key of path.split('/').splice(1)) {
if (_key === '') {
return input;
}
result = (result as Folder)[_key];
if (result == null) return null;
}
return result;
}
console.log('FOUND:', findDirOrFile('/root/c', fof));
console.log('FOUND:', findDirOrFile('/f', fof));
実行すると、
"FOUND:", {
"__type": "folder",
"createdAt": "2025-07-18T17:01:16.949Z",
"updatedAt": "2025-07-18T17:01:16.949Z",
"e.txt": {
"__type": "file",
"createdAt": "2025-07-18T17:01:16.949Z",
"updatedAt": "2025-07-18T17:01:16.949Z"
}
}
"FOUND:", null
というようになります。
ちゃんと
/root/c
また、
/f
まとめ
今回は、Typescriptの高度な型プログラミングを活用し、ファイルシステムのフォルダ構造を型で表現するテクニックを考えてみました。
* **回帰的な型定義**:
フォルダが子要素として自身(フォルダ)やファイルを持つ構造を表現するために、
型自身を再帰的に参照する定義を使う
* **Mapped型の活用**:
フォルダ内の動的なファイル名やフォルダ名をプロパティとして表現するために、
Mapped型を利用している
* **実践的な型プログラミング**:
抽象的なファイルシステムを、Typescriptの型システムで具体的な型に落とし込んで表現できる
もっと短くスマートに表現できるかもしれませんが、深入りすると脳トレクイズのように実用性の話からかけ離れていくため、個人的にはここらへんで良しとします。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー