Typescriptでファイルシステムのフォルダ構造を型で表現する


※ 当ページには【広告/PR】を含む場合があります。
2025/07/15
TypescriptでGraphQL Code Generatorから自動生成されるクエリ宣言から部分型を抽出する
Typescriptで学ぶ!再帰とMapped型でDOM構造を表現するテクニック
蛸壺の技術ブログ|Typescriptでファイルシステムのフォルダ構造を型で表現する

Typescriptの型プログラミングの応用的な利用例として、ファイルシステムのフォルダ構造を型で表現するテクニックについて考えてみましょう。

以前の記事では、GraphQLのスキーマ型をTypescriptの型でどう扱うかを紹介しました。

合同会社タコスキングダム|蛸壺の技術ブログ
TypescriptでGraphQL Code Generatorから自動生成されるクエリ宣言から部分型を抽出する

Typescripで「GraphQL Code Generator」から自動生成したクエリ型の部分型を取り出す

今回はより身近な例として、一般的なファイルシステムのフォルダ構造をTypescriptの型で表現することに挑戦します。Typescriptの実践的な型プログラミングの利用例として、その強力さを感じていただければ幸いです。


合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

フォルダ構造について

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,
}
        

合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

フォルダ構造の型を設計する

当然、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'かを区別し、createdAtupdatedAtで適当な作成日時と更新日時を保持します。

次に、
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というファイルが存在する、という階層構造が型によって表現されていることが分かります。


合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

ユーティリティメソッドの実装例

では先程の型の定義を利用したメソッドを作ってみましょう。

以下のメソッドは、先程作ったフォルダ構造の型のインスタンスからフォルダかファイルのパスを一覧として表示させるものです。

            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というように存在しないフォルダかファイルを指定した場合には、nullが返っているのも分かります。


合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

まとめ

今回は、Typescriptの高度な型プログラミングを活用し、ファイルシステムのフォルダ構造を型で表現するテクニックを考えてみました。

            *   **回帰的な型定義**:
    フォルダが子要素として自身(フォルダ)やファイルを持つ構造を表現するために、
    型自身を再帰的に参照する定義を使う
*   **Mapped型の活用**:
    フォルダ内の動的なファイル名やフォルダ名をプロパティとして表現するために、
    Mapped型を利用している
*   **実践的な型プログラミング**:
    抽象的なファイルシステムを、Typescriptの型システムで具体的な型に落とし込んで表現できる
        
もっと短くスマートに表現できるかもしれませんが、深入りすると脳トレクイズのように実用性の話からかけ離れていくため、個人的にはここらへんで良しとします。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。

合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き