Protobuf to TypeScript コンバーター
.proto スキーマを貼り付けると、proto3 JSON マッピング仕様に従って 64 ビット整数を文字列にするなど、適切な型を持つ TypeScript インターフェースが得られます。
入力 (.proto スキーマ)
出力 (TypeScript)
このツールでできること
Protocol Buffers のスキーマがあって、それを配信する gRPC または HTTP トランスコード済みのバックエンドと通信する TypeScript のフロントエンドを書いている、という状況で使えます。公式の codegen ツールチェーン(protoc と ts-proto または protobuf-ts)はツールのインストール、プラグインの設定、ビルド工程の組み込みが必要です。このコンバーターは同じ仕事をブラウザの中で済ませます — 貼り付けて、出力をコピーして、プロジェクトに放り込むだけ。
型のマッピングは実際のコードベースで人間が書く型に倣っています:string/bytes → string/Uint8Array、bool → boolean、小さい数値型(int32、uint32、float、double)→ number、64 ビット整数型(int64、uint64、fixed64、sfixed64、sint64)→ string、これは proto3 JSON マッピング仕様 に合わせたものです。repeated T は T[]、map<K, V> は Record<K, V>、ネストされたメッセージはネストされた interface 宣言になります。
フィールド名は snake_case(Protobuf の慣例)から camelCase(JavaScript/TypeScript の慣例)に変換されます — これは proto3 JSON エンコーダー のデフォルト動作と一致します。enum は文字列リテラルの union 型になります(type OrderStatus = 'ORDER_STATUS_UNSPECIFIED' | 'ORDER_STATUS_PENDING' | ...)。これは多くの TypeScript コードベースが実際に欲しがる形で、enum のランタイムオーバーヘッドが要りません。コンバーターは完全にブラウザ内で動作します。スキーマがページの外に出ることはありません。
使い方
3 ステップ。出力は数秒で <code>.ts</code> ファイルに貼り付けられる状態になります。
.proto スキーマを貼り付ける
左のエディタにスキーマを入れます。先頭の syntax = "proto3"; はあっても問題ありませんが任意です。パーサーはネストされた message ブロック、enum 宣言、oneof、map<K, V>、フィールドオプションを処理します。import は認識されますがスキップされる — 必要なら import している型を直接貼り付けてください。
フィールド名の変換は自動です:.proto の order_id は TypeScript の orderId になります。メッセージと enum の名前はそのまま(既に PascalCase なので)。
出力を読む
右側:各メッセージに対する export interface と、各 enum に対する文字列リテラル union の export type。ネストされた型は親より先に来るので、ファイルは宣言順になっています。プロジェクトに追加して、gRPC クライアントや fetch ハンドラーからインターフェースをインポートしてください。
型を使う
fetch / gRPC-Web / Connect-RPC クライアントにインターフェースをつなぎます。形は proto3 JSON エンコーディング に合っているので、JSON レスポンスは手動変換なしでそのまま型付き形にパースされます。サーバーが非標準の JSON エンコーディングを使う場合は int64 の扱いを調整してください。
実際に時間を節約できる場面
新しい gRPC フロントエンドの型を試作する
既存の gRPC サービスの上に新しい TS アプリを構築している。フル codegen はまだ要らない — fetch 呼び出しに型を付けるためのインターフェースの形だけが欲しい。.proto を貼り付けて、出力を types.ts に放り込めば、もう型付きです。
Protobuf API の変更をレビューする
バックエンドの仲間がメッセージにフィールドを追加した。ビルドを走らせずに、フロントエンドの型にどう影響するか見たい。新しい .proto を貼り付けて、TypeScript の出力を現在の型と diff して、的を絞ったレビューコメントを残せます。
生成された型を照合する
ビルドで protobuf-ts や ts-proto を使っていて、それぞれ独自の慣例で型を生成している。素の TS インターフェースがどう見えるかの綺麗なリファレンスとして、ここにスキーマを貼り付けるのが便利です — ドキュメントや移行計画に役立ちます。
使い捨てのスクリプトと一回限りの統合
gRPC ゲートウェイに JSON を POST する短い Node スクリプトを書いている。30 行のために Protobuf のフルツールチェーンを構築するのはやり過ぎ。ここからインターフェースを取れば、儀式なしで型安全が手に入ります。
よくある質問
スキーマはどこかに送信されますか?
いいえ。パーサーと TS エミッターは完全にブラウザ内で JavaScript として動きます。DevTools を開いて Network タブを見ながら貼り付けてみてください — リクエストはゼロです。スキーマに内部の型名、パッケージパス、第三者サービスに送りたくないものが含まれているときに便利です。
なぜ int64 フィールドは string で型付けされるのですか?
JavaScript の Number は IEEE-754 倍精度浮動小数で、2^53 を超えると精度を失います。公式の proto3 JSON マッピング は int64、uint64、fixed64、sfixed64、sint64 を JSON 文字列としてエンコードすることを要求します。なので TS インターフェースはそれらに string を使います — サーバーが実際に送るものに合っています。代わりに bigint が欲しければ、出力を find-replace してください。
なぜ enum は TS の enum ではなく文字列 union なのですか?
今の TypeScript プロジェクトの多くは TS の enum よりも文字列リテラル union を好みます — ランタイムコストなし、tree-shaking が効きやすく、proto3 JSON エンコーディング(enum を文字列名でシリアライズする)と一致します。const enum や数値 enum が欲しければ、union からの変換は機械的にできます。
map<K, V> はどう扱いますか?
Record<K, V> としてレンダリングされます。string でないキーを持つ Protobuf マップ(例えば map<int32, string>)は Record<number, string> になります。JSON は string キーしか持てないので、proto が int と言っていてもランタイムでは文字列になります — これは proto3 JSON 仕様の癖で、コンバーターのせいではありません。
フィールドはオプショナルとしてマークされますか?
いいえ。proto3 のフィールドは常に JSON 出力に含まれます(デフォルト値付き — 空文字列、0、false、[]、{})。なので TypeScript インターフェースはすべてのフィールドを必須としてマークします。実際にオプショナルなフィールドが欲しい場合(ランタイムが省略する可能性があるなど)、各フィールド名のあとに手動で ? を付けてください。
oneof は扱いますか?
oneof の各フィールドは普通のインターフェースのフィールドとして出力されます。出力は oneof が示す「ちょうど 1 つ」の制約を強制しません — そのためには判別可能な union が必要で、それはランタイムのセマンティクスに依存します。より厳密な型が必要な場合は出力を手で編集してください。
関連ツール
Protobuf、JSON、TypeScript を扱うなら、これらと組み合わせるのが便利です: