はじめに
TypeScript実践編では、型安全な実際の開発スキルを学びます。第1回はファイル操作です。
Node.jsのfsモジュールを型付きで使用します。
型定義のインストール
npm install --save-dev @types/node
基本的なファイル読み書き
ファイルを読む
import { readFile } from 'fs/promises';
import { createReadStream } from 'fs';
import * as readline from 'readline';
// ファイル全体を読み込む
async function readTextFile(filePath: string): Promise<string> {
return await readFile(filePath, 'utf-8');
}
// 1行ずつ処理する
async function readLines(filePath: string): Promise<string[]> {
const lines: string[] = [];
const stream = createReadStream(filePath, 'utf-8');
const rl = readline.createInterface({ input: stream });
for await (const line of rl) {
lines.push(line);
}
return lines;
}
ファイルに書き込む
import { writeFile, appendFile } from 'fs/promises';
// 新規作成・上書き
async function writeTextFile(filePath: string, content: string): Promise<void> {
await writeFile(filePath, content, 'utf-8');
}
// 追記
async function appendToFile(filePath: string, content: string): Promise<void> {
await appendFile(filePath, content, 'utf-8');
}
// 複数行を書き込み
async function writeLines(filePath: string, lines: string[]): Promise<void> {
await writeFile(filePath, lines.join('\n') + '\n', 'utf-8');
}
型安全なJSON操作
TypeScriptの強みを活かした型安全なJSON操作です。
import { readFile, writeFile } from 'fs/promises';
// 型定義
interface User {
name: string;
age: number;
skills: string[];
}
interface Config {
version: string;
debug: boolean;
users: User[];
}
// 型安全なJSON読み込み
async function readJson<T>(filePath: string): Promise<T> {
const content = await readFile(filePath, 'utf-8');
return JSON.parse(content) as T;
}
// 型安全なJSON書き込み
async function writeJson<T>(filePath: string, data: T): Promise<void> {
await writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
}
// 使用例
async function example() {
// 型推論が効く
const config = await readJson<Config>('config.json');
console.log(config.version); // 補完が効く
const user: User = {
name: '太郎',
age: 25,
skills: ['TypeScript', 'React']
};
await writeJson('user.json', user);
}
バリデーション付きJSON読み込み
実行時の型チェックにはzodなどを使用します。
import { readFile } from 'fs/promises';
import { z } from 'zod';
// スキーマ定義
const UserSchema = z.object({
name: z.string(),
age: z.number().min(0),
skills: z.array(z.string())
});
type User = z.infer<typeof UserSchema>;
// バリデーション付き読み込み
async function readAndValidateJson<T>(
filePath: string,
schema: z.ZodType<T>
): Promise<T> {
const content = await readFile(filePath, 'utf-8');
const data = JSON.parse(content);
return schema.parse(data); // バリデーションエラーで例外
}
// 使用例
const user = await readAndValidateJson('user.json', UserSchema);
CSV操作
import { readFile, writeFile } from 'fs/promises';
interface CsvRow {
[key: string]: string;
}
// CSVを読み込む
async function readCsv(filePath: string): Promise<CsvRow[]> {
const content = await readFile(filePath, 'utf-8');
const lines = content.trim().split('\n');
const headers = lines[0].split(',');
return lines.slice(1).map(line => {
const values = line.split(',');
return headers.reduce<CsvRow>((obj, header, i) => {
obj[header] = values[i];
return obj;
}, {});
});
}
// 型付きCSV読み込み
interface Employee {
name: string;
age: number;
department: string;
}
async function readEmployeeCsv(filePath: string): Promise<Employee[]> {
const rows = await readCsv(filePath);
return rows.map(row => ({
name: row.name,
age: parseInt(row.age, 10),
department: row.department
}));
}
// CSVに書き込む
async function writeCsv<T extends Record<string, unknown>>(
filePath: string,
data: T[]
): Promise<void> {
if (data.length === 0) return;
const headers = Object.keys(data[0]);
const lines = [
headers.join(','),
...data.map(row => headers.map(h => String(row[h])).join(','))
];
await writeFile(filePath, lines.join('\n'), 'utf-8');
}
パス操作
import * as path from 'path';
// パスの結合
const filePath: string = path.join('documents', 'report.txt');
// パス情報の取得
interface PathInfo {
dir: string;
name: string;
ext: string;
base: string;
}
function getPathInfo(filePath: string): PathInfo {
const parsed = path.parse(filePath);
return {
dir: parsed.dir,
name: parsed.name,
ext: parsed.ext,
base: parsed.base
};
}
// 絶対パスに変換
const absolutePath: string = path.resolve('file.txt');
ディレクトリ操作
import {
mkdir,
readdir,
stat,
unlink,
rm,
copyFile,
rename,
access
} from 'fs/promises';
import { Dirent, Stats } from 'fs';
// 存在確認
async function exists(filePath: string): Promise<boolean> {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
// ファイル情報の取得
async function getFileInfo(filePath: string): Promise<{
isFile: boolean;
isDirectory: boolean;
size: number;
}> {
const stats: Stats = await stat(filePath);
return {
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
size: stats.size
};
}
// ディレクトリ作成
async function createDir(dirPath: string): Promise<void> {
await mkdir(dirPath, { recursive: true });
}
// ディレクトリ内のファイル一覧
async function listFiles(dirPath: string): Promise<string[]> {
return await readdir(dirPath);
}
// 詳細情報付き一覧
async function listFilesWithInfo(dirPath: string): Promise<{
name: string;
isDirectory: boolean;
}[]> {
const entries: Dirent[] = await readdir(dirPath, { withFileTypes: true });
return entries.map(entry => ({
name: entry.name,
isDirectory: entry.isDirectory()
}));
}
// ファイル操作
async function fileOperations(): Promise<void> {
await copyFile('src.txt', 'dst.txt');
await rename('old.txt', 'new.txt');
await unlink('file.txt');
await rm('folder', { recursive: true });
}
ユーティリティ関数
汎用的なファイル操作ユーティリティです。
import { readFile, writeFile, mkdir } from 'fs/promises';
import * as path from 'path';
// ディレクトリを作成してからファイルを書き込む
async function writeFileWithDir(
filePath: string,
content: string
): Promise<void> {
const dir = path.dirname(filePath);
await mkdir(dir, { recursive: true });
await writeFile(filePath, content, 'utf-8');
}
// ファイルを読み込み、なければデフォルト値を返す
async function readFileOrDefault(
filePath: string,
defaultValue: string
): Promise<string> {
try {
return await readFile(filePath, 'utf-8');
} catch {
return defaultValue;
}
}
// JSONを読み込み、なければデフォルト値を返す
async function readJsonOrDefault<T>(
filePath: string,
defaultValue: T
): Promise<T> {
try {
const content = await readFile(filePath, 'utf-8');
return JSON.parse(content) as T;
} catch {
return defaultValue;
}
}
実践例:設定ファイル管理
import { readFile, writeFile } from 'fs/promises';
interface AppConfig {
port: number;
database: {
host: string;
port: number;
name: string;
};
logging: {
level: 'debug' | 'info' | 'warn' | 'error';
file?: string;
};
}
const DEFAULT_CONFIG: AppConfig = {
port: 3000,
database: {
host: 'localhost',
port: 5432,
name: 'myapp'
},
logging: {
level: 'info'
}
};
class ConfigManager {
private config: AppConfig;
private configPath: string;
constructor(configPath: string) {
this.configPath = configPath;
this.config = DEFAULT_CONFIG;
}
async load(): Promise<void> {
try {
const content = await readFile(this.configPath, 'utf-8');
this.config = { ...DEFAULT_CONFIG, ...JSON.parse(content) };
} catch {
// ファイルがなければデフォルト設定を使用
await this.save();
}
}
async save(): Promise<void> {
await writeFile(
this.configPath,
JSON.stringify(this.config, null, 2),
'utf-8'
);
}
get<K extends keyof AppConfig>(key: K): AppConfig[K] {
return this.config[key];
}
set<K extends keyof AppConfig>(key: K, value: AppConfig[K]): void {
this.config[key] = value;
}
}
// 使用例
async function main() {
const config = new ConfigManager('config.json');
await config.load();
console.log(config.get('port')); // 3000
config.set('port', 8080);
await config.save();
}
まとめ
fs/promisesで型付き非同期ファイル操作- ジェネリクスで型安全なJSON読み書き
- zodでランタイムバリデーション
pathモジュールでパス操作- ユーティリティ関数で再利用性を高める
次回は例外処理について学びます。