
TypeScriptインターフェースを手書きするのをやめる:JSONジェネレーターを使いましょう
📷 Roman Synkevych / PexelsTypeScriptインターフェースを手書きするのをやめる:JSONジェネレーターを使いましょう
JSONペイロードからTypeScriptインターフェースを手書きするのは面倒でエラーが起きやすいです。自動化する方法と注意すべきことを解説します。
APIから受け取ったJSONペイロードからTypeScriptインターフェースを手書きするために20分費やした経験があれば、これが重要に見えるが、ほぼ完全に機械的な作業の1つであることをすでに知っています。考えているのではありません。ただフィールド名をコピーして型を推論しているだけです。機械が行うべき作業です。
それでも、多くの開発者が手書きを続けています。このガイドでは、それを止める価値がある理由、JSON to TypeScriptジェネレーターが実際に生成するもの、そして — より重要なことに — できないことについて解説し、どこで引き継ぐべきかがわかるようにします。
TypeScriptインターフェースが実際に重要な理由
ツールに入る前に、型付きインターフェースから「バグを捕まえる」以上に何を得るかを正確に知る価値があります。
IDEの自動補完が過小評価されているものです。 APIレスポンスが型付けされると、エディターはどのフィールドが存在し、ネストされたオブジェクトがどのような形状をしているかを正確に知ることができます。user.profilePictureなのかuser.profile_pictureなのかuser.avatarなのかを推測するのをやめられます。
リファクタリングがはるかに安全になります。 バックエンドチームがフィールド名をuserIdからuser_idに変更すると、コードベース内の古い名前を参照するすべての場所が、ユーザーが実行時にエラーに遭遇するのではなく、コンパイル時に壊れます。
陳腐化しないドキュメント。 適切に命名されたインターフェースは、しばしば書かれたドキュメントよりも役立ちます。
手動プロセスとジェネレーターの使用
中程度の複雑さのAPIレスポンスに対して手書きで型を書くとどうなるか:
{
"user": {
"id": 1042,
"email": "alice@example.com",
"name": "Alice Nguyen",
"roles": ["admin", "editor"],
"profile": {
"bio": "Frontend developer based in Berlin.",
"avatarUrl": "https://cdn.example.com/avatars/1042.png",
"joinedAt": "2023-06-15T08:30:00Z"
},
"settings": {
"theme": "dark",
"notifications": {
"email": true,
"push": false
}
}
},
"meta": {
"requestId": "abc-123",
"timestamp": 1712486400
}
}
同じJSONをJSON to TypeScriptツールに貼り付けると、1秒未満でこれが得られます:
export interface Root {
user: User;
meta: Meta;
}
export interface User {
id: number;
email: string;
name: string;
roles: string[];
profile: Profile;
settings: Settings;
}
export interface Profile {
bio: string;
avatarUrl: string;
joinedAt: string;
}
export interface Settings {
theme: string;
notifications: Notifications;
}
export interface Notifications {
email: boolean;
push: boolean;
}
export interface Meta {
requestId: string;
timestamp: number;
}
RootをUserResponseのような意味のある名前に変えるだけで完了です。
実際の使用ケース
APIレスポンスの型付け
これが主な使用ケースです。サードパーティのAPI — 決済処理業者、CRM、天気サービス — と統合し、返ってくるものに対する型が必要です。
設定ファイル
アプリが実行時にJSON設定ファイルを読む場合、TypeScriptインターフェースが欲しくなります:
// config.interface.ts (config.jsonから生成)
export interface AppConfig {
database: Database;
redis: Redis;
featureFlags: FeatureFlags;
}
テストデータのモック
テストを書くとき、型付きモックオブジェクトが必要なことがよくあります。実際のデータからインターフェースを生成して、それに対して型付きモックを作成しましょう。
トリッキーなケースの処理
Null値
JSONはnullを値として許可し、ジェネレーターはそれらのフィールドをnullとして型付けします。しかし実際には、nullフィールドは通常フィールドがオプションであることを意味します:
interface User {
middleName: string | null;
}
日付
JSONにはネイティブのDate型がありません。"2023-06-15T08:30:00Z"はJSONの観点ではstringです。一部のチームはtype aliasを作成します:
type ISODateString = string;
判別可能なユニオン
APIがtypeやstatusフィールドによって異なる形状を返すことがあります:
type ApiResult = SuccessResult | ErrorResult;
interface SuccessResult {
type: "success";
data: OrderData;
}
interface ErrorResult {
type: "error";
code: string;
message: string;
}
ベストプラクティス
ルートインターフェースに意味のある名前を付ける
ジェネレーターはRootや汎用的なものに名前を付けます。コミット前に必ず名前を変更してください。
専用ファイルに型を保持する
src/
types/
api/
user.types.ts
order.types.ts
config.types.ts
ランタイム検証のためのZod
TypeScriptの型はランタイムで消去されます。ランタイム保証が必要な場合はZodを検討してください:
import { z } from "zod";
const UserSchema = z.object({
id: z.number(),
email: z.string().email(),
name: z.string(),
});
type User = z.infer<typeof UserSchema>;
ジェネレーターができないこと
ビジネスロジックを知らない。 numberとして型付けされたフィールドが実際には常に正の整数である可能性があります。
スナップショットで動作する。 生成された型は一つのデータサンプルを反映します。実際のAPIは進化します。
必須とオプションを区別できない。 サンプルのすべてのフィールドが必須として扱われます。
すべての文字列を文字列として扱う。 "pending" | "active" | "cancelled"のみを含むstatusフィールドは、より精度の高い型の代わりにstringとして型付けされます。
これらはツールを避ける理由ではありません — 出力を完成品ではなくドラフトとして扱うべき理由です。ジェネレーターは退屈な作業の80%を処理します。残りの20%はデータの実際の意味を理解する必要があります。
まとめ
ほとんどのプロジェクトで意味のあるワークフロー:
- API、設定、または他のソースから実際の(または代表的な)JSONサンプルを取得する
- JSON to TypeScriptジェネレーターに貼り付ける
Rootを意味のものに変更する- 適切な
*.types.tsファイルに出力をコピー - 各フィールドを見直す:オプションのフィールドに
?をマーク、必要に応じて| nullを追加 - ソースJSONが圧縮されて読みにくい場合はJSONフォーマッターを使用してから型を生成
- YAML設定も維持している場合はJSON to YAMLコンバーターが同期に役立ちます
手書きの型が本質的に優れているわけではありません — ただ遅くてタイポが起きやすいだけです。ツールを使って、出力を見直して、実際に人間が必要な部分に時間を使いましょう。