入力(.proto スキーマ)

出力(Java)

このツールでできること

Protocol Buffers のスキーマがあって、それらのメッセージを消費する Java サービスがある — gRPC 経由かもしれないし、HTTP 上の JSON 経由かもしれません。公式の protocJava プラグインと一緒に走らせると、Builder パターンの生成クラスが手に入りますが、本番には良くてもスケッチ・プロトタイピング・JSON を POJO に手動でマッピングするには重すぎます。このコンバーターはシンプルな Java クラスを出力します — public フィールド、妥当なデフォルト、メッセージあたり 1 クラス、enum は別の型として。

型マッピングは現場で書かれる Java の感覚に合わせています:stringStringboolbooleanint32/sint32/sfixed32intint64/sint64/sfixed64longdoubledoublefloatfloatbytesbyte[]。Java には符号なし数値型がないので、uint32/fixed32int に、uint64/fixed64long に落ちます — ほとんどの用途で十分。本当に最上位ビットが必要なら境界で Integer.toUnsignedLong を使ってください。repeated TList<T> に、map<K, V>Map<K, V> になります。どちらも java.util から。

フィールド名は snake_case(Protobuf の慣習)から camelCase(Java の慣習)に変換されます — protoc がやることと一致しますし、proto3 JSON マッピングもそうです。enum はトップレベルの public enum 型になり、ワイヤー上の整数値は final フィールドとして保持されるので、いつか protoc 生成コードに切り替えてもラウンドトリップ可能です。ネストされたメッセージはトップレベルクラスにフラット化されるので、出力を分割するときにそれぞれを自分のファイルに移せます。すべてブラウザ内で動作 — スキーマはページの外に出ません。

使い方

3 ステップ。出力は Java プロジェクトにそのまま入れられます。

1

.proto スキーマを貼り付け

左のエディタにスキーマを入れます。先頭の syntax = "proto3"; はあっても無くてもかまいません。パーサーはネストされた message ブロック、enum 宣言、oneofmap<K, V>、フィールドオプションを扱います。import ディレクティブは認識されますがスキップされます — 必要ならインポート対象の型をインラインで貼ってください。

フィールド名の変換は自動です:.protoorder_id は Java では orderId になります。メッセージと enum 名はそのまま(すでに PascalCase)。

2

出力を読む

右側:1 つの .java ブロックに、まず enum 全部、続いて宣言順にクラス全部。各クラスはプリミティブ用のデフォルト値(00.0false"")が入った public フィールドを持ち、参照型(ListMap、メッセージ参照)は埋めるまで null のまま。java.util.Listjava.util.Map の import は、スキーマが必要とする場合だけ追加されます。

3

プロジェクトに入れる

各クラスをそれぞれの .java ファイルにコピーします(Java はファイルあたり 1 つの public クラスを要求します)。package 宣言を加えて、お好みの JSON デシリアライザに繋ぎます — Jackson はデフォルトで public フィールドを拾いますし、proto3 JSON マッピングは camelCase なので、フィールド名はもう一致しています。getter/setter が好みなら、IntelliJ の "Encapsulate Fields" リファクタリングが 1 ストロークで処理してくれます。

本当に時間を節約できる場面

gRPC サービス向けの Java クライアントをスケッチする

既存の gRPC バックエンド(Spring Boot サービスかもしれないし Quarkus かもしれない)に対して Java クライアントをスパイクしていて、まだ protoc Maven/Gradle プラグインのフルセットアップはしたくない。スキーマを貼り、src/main/java/dto にクラスを置き、Jackson で JSON レスポンスをデシリアライズし、プロトタイプを出荷。

proto と一致する DTO を手作りする

チームはワイヤー上の真実の源として Protobuf を使っているけれど、消費する Java サービスは 3〜4 フィールドだけ必要で、Message/Builder 依存は欲しくない。スキーマを貼って、要らないフィールドを消せば、単独でコンパイルできるシンプルな DTO ができ上がり。

Protobuf API の変更をレビューする

バックエンドの同僚がメッセージにフィールドを追加した。ビルドを走らせずに Java POJO への影響を見たい。新しい .proto を貼り、Java 出力を現在のクラスと diff し、ピンポイントなレビューコメントを残す。

protoc 生成出力との突き合わせ

ビルドは公式の protoc Java プラグインを使っていて、Builder パターンのクラスを生成する。シンプルな Java がどう見えるかのきれいな参照としてここにスキーマを貼る。ドキュメント、オンボーディング、データクラスを好む Kotlin チームメイト向けに便利。

よくある質問

スキーマはどこかに送信されますか?

いいえ。パーサーと Java エミッタは完全にブラウザ内で JavaScript として動作します。貼り付けながら DevTools を開いて Network タブを見てください — リクエストはゼロです。スキーマに内部パッケージパス、型名、その他サードパーティに送りたくないものが含まれているときに便利です。

なぜ getter/setter ではなく public フィールド?

理由は 2 つ。1 つは、出力は適応用の出発点として作られているから — public フィールドはコンパイルが通る最小のもので、いつでも IDE の "Encapsulate Fields" を走らせられます。もう 1 つは、最も一般的な消費者は Jackson のデシリアライズで、これは public フィールドでそのまま動くからです。record、不変型、Lombok @Data クラスが必要なら、出力を貼ってリファクタリングしてください。

uint32 と uint64 はどう扱われますか?

Java には符号なし整数型がないので、uint32/fixed32int に、uint64/fixed64long にマップされます。符号付きの範囲に収まる値ならこれで問題なし。Integer.MAX_VALUELong.MAX_VALUE を超える値については負の数が見えます。標準的な対処は境界での Integer.toUnsignedLong(x)、または java.lang.Long の算術ヘルパーを使うこと — 符号なしヘルパーについては Java 21 API ドキュメントを参照してください。

なぜ int64 フィールドは String ではなく long のまま?

TypeScript と JSON は 53 ビットの精度上限を持つので、proto3 JSON 仕様は精度を保つために 64 ビット整数を文字列としてエンコードします。Java の long は精度を失わない真の 64 ビット符号付き整数なので、Java 型は long のままにしています。サーバーがすでに int64 を文字列としてエンコードした JSON をデシリアライズしているなら、Jackson を @JsonFormat やカスタムデシリアライザで設定してください。基底の型は変える必要がありません。

ネストされたメッセージはどう扱われますか?

各ネストされたメッセージはトップレベルクラスにフラット化されます。Java の慣習はファイルあたり 1 つの public クラスなので、フラットなトップレベル型のほうが、各クラスをそれぞれの .java ファイルにコピーするときに分割しやすいです。protoc が出すスタイルの static inner class が良ければ、出力を貼ってネストされたクラスを親の中に移動してください — フィールド参照はすでに葉の名前を使っているので、同じスコープに入れば解決します。

フィールドは optional として印が付きますか?

いいえ — proto3 のフィールドはワイヤーフォーマット上で常にデフォルトを持つので、プリミティブフィールドは Java のゼロ値(00.0false"")に初期化されます。参照型(ListMap、ネストされたメッセージ、byte[])は null から始まるので、要素を追加する前に初期化することを忘れずに。明示的な Optional<T> ラッパーが欲しい場合は、それは手動の手順です。

oneof は扱えますか?

oneof の各フィールドは普通のクラスフィールドとして出力されます。出力は oneof が示唆する「ちょうど 1 つ」の制約を強制しません — そのためには sealed 型階層か実行時チェックが必要で、どちらも単純な POJO には合いません。より厳密なモデリングが欲しいなら、出力を取って oneof のフィールドを sealed インターフェースとケースごとの record に変換してください。

公式の protobuf-java ランタイムでこれらのクラスを使えますか?

直接は使えません — 公式ランタイムは BuilderparseFrom、その他 com.google.protobuf.MessageOrBuilder 契約を備えた protoc 生成クラスを期待します。このツールが出すクラスは単純な POJO で、JSON シリアライズ(Jackson、Gson、Moshi)向けです。バイナリワイヤーフォーマットには公式の codegen が引き続き必要 — セットアップは Java チュートリアルを参照してください。

関連ツール

Protobuf、JSON、Java を扱うなら、これらの組み合わせが効きます: