注: このサイトは非推奨になりました。このサイトは 2023 年 1 月 31 日を過ぎると停止され、トラフィックは https://protobuf.dev の新しいサイトにリダイレクトされます。その間、更新は protobuf.dev のみに行われます。

言語ガイド

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

このガイドでは、プロトコル バッファ言語を使用して .proto ファイル構文などのプロトコル バッファ データを構造化する方法と、.proto ファイルからデータアクセス クラスを生成する方法について説明します。プロトコル バッファ言語の proto2 バージョンについて説明します。proto3 の構文については、Proto3 言語ガイドをご覧ください。

これはリファレンス ガイドです。このドキュメントで説明するさまざまな機能を使用する詳しい手順については、選択した言語のチュートリアルをご覧ください。

メッセージ タイプを定義する

まず、非常にシンプルな例を見てみましょう。検索リクエスト メッセージの形式を定義するとします。各検索リクエストには、クエリ文字列、関心のある結果のページ、ページあたりの結果の数が含まれます。メッセージ タイプの定義に使用する .proto ファイルは次のとおりです。


message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}

SearchRequest メッセージの定義では、3 つのフィールド(名前と値のペア)を指定します。このタイプのメッセージに含めるデータごとに 1 つずつあります。各フィールドには名前と型があります。

フィールド タイプの指定

上記の例では、すべてのフィールドがスカラー型(2 つの整数(page_numberresult_per_page)と文字列(query))です。ただし、列挙型やその他のメッセージ型など、フィールドの複合型を指定することもできます。

フィールド番号の割り当て

このように、メッセージ定義の各フィールドには一意の番号があります。 これらの数値は、メッセージ バイナリ形式でフィールドを識別するために使用されます。メッセージ タイプの使用後に変更しないでください。1 から 15 の範囲の数値は、フィールド番号やフィールドの型など、エンコードに 1 バイトかかります(詳しくはプロトコル バッファ エンコードをご覧ください)。16 ~ 2047 のフィールド番号は 2 バイトです。したがって、頻繁に発生するメッセージ要素には、フィールド番号 1 ~ 15 を予約する必要があります。今後追加される可能性がある、頻繁に発生する要素には、いくらかスペースを残すようにしてください。

指定できる最小のフィールド番号は 1、最大値は 229 - 1 または 536,870,911 です。また、19000 ~ 19999(FieldDescriptor::kFirstReservedNumberFieldDescriptor::kLastReservedNumber)はプロトコル バッファの実装のために予約されているため、使用できません。.proto でこれらの予約済み番号のいずれかを使用すると、プロトコル バッファ コンパイラはエラーを出します。同様に、予約済みのフィールド番号は使用できません。

フィールド ルールの指定

メッセージ フィールドは次のいずれかで指定します。

  • required: 整形式のメッセージでは、このフィールドの 1 つのみを指定する必要があります。
  • optional: 整形式のメッセージでは、このフィールドを 0 または 1 つ指定できます(複数指定することはできません)。
  • repeated: このフィールドは、正しい形式のメッセージで何度でも繰り返すことができます(ゼロを含む)。繰り返し値の順序は保持されます。

歴史的な理由で、スカラー数値型の repeated フィールド(int32int64enum など)は、効率的にエンコードされません。新しいコードでは、特殊なオプション [packed = true] を使用して、より効率的にエンコードできるようにする必要があります。例:

repeated int32 samples = 4 [packed = true];
repeated ProtoEnum results = 5 [packed = true];

packed エンコードの詳細については、プロトコル バッファ エンコードをご覧ください。

Required Forever フィールドを required としてマークする際は注意が必要です。必須フィールドの入力または送信を停止する場合、このフィールドを省略可能なフィールドに変更することは問題になります。古い読者は、このフィールドのないメッセージは未完了とみなし、誤って拒否または破棄される可能性があります。代わりに、バッファにアプリケーション固有のカスタム検証ルーチンを作成することを検討してください。

必須フィールドに 2 番目の値を追加すると、2 つ目の問題が表示されます。この場合、認識されない列挙値は欠落しているものと同様に扱われ、必要な値の確認も失敗します。

メッセージ タイプの追加

1 つの .proto ファイルで複数のメッセージ タイプを定義できます。これは、複数の関連メッセージを定義する場合に便利です。たとえば、SearchResponse メッセージ タイプに対応する返信メッセージ形式を定義する場合は、同じ .proto に追加できます。


message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

メッセージを組み合わせると肥大化を引き起こします。1 つの .proto ファイルで複数のメッセージ タイプ(message、enum、service など)を定義することはできますが、さまざまな依存関係を持つ多数のメッセージが 1 つのファイルで定義されていると、依存関係の肥大化につながる可能性があります。.proto ファイルごとに可能な限り少ないメッセージ タイプを含めることをおすすめします。

コメントを追加する

.proto ファイルにコメントを追加するには、C/C++ スタイルの // および /* ... */ 構文を使用します。


/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;  // Which page number do we want?
  optional int32 result_per_page = 3;  // Number of results to return per page.
}

予約済みフィールド

フィールドを完全に削除するか、コメントアウトしてメッセージ型を更新した場合、将来のユーザーは、型を独自に更新するときにフィールド番号を再利用できます。これにより、後でデータの破損やプライバシー バグなど、同じ.protoの古いバージョンを読み込むと、重大な問題が発生することがあります。これを回避する方法の一つとして、削除されたフィールドのフィールド番号(および、JSON シリアル化で問題を引き起こす可能性がある名前)を指定します。reserved今後のユーザーがこれらのフィールド識別子を使用しようとすると、プロトコル バッファ コンパイラはエラーになります。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

予約済みフィールド番号の範囲は含まれます(9 to 119, 10, 11 と同じです)。フィールド名とフィールド番号を同じ reserved ステートメント内で混在させることはできません。

.proto から生成される内容

.protoプロトコル バッファ コンパイラを実行すると、コンパイラは、記述されたメッセージ タイプに対応するコード(フィールド値の取得と設定、出力ストリームへのメッセージのシリアル化、入力ストリームからのメッセージの解析など)を、選択した言語で生成します。

  • C++ の場合、コンパイラは各 .proto から .h ファイルと .cc ファイルを生成し、ファイル内に記述されているメッセージ タイプごとにクラスを作成します。
  • Java の場合、コンパイラはメッセージ タイプごとにクラスを含む .java ファイルと、メッセージ クラス インスタンスを作成するための特別な Builder クラスを生成します。
  • Python は若干異なります。Python コンパイラは、.proto 内の各メッセージ タイプの静的記述子を使用してモジュールを生成します。このモジュールは、メタクラスで使用され、実行時に必要な Python データ アクセス クラスを作成します。
  • Go の場合、コンパイラはファイル内のメッセージ タイプごとに .pb.go ファイルを生成します。

各言語の API の使用方法については、選択した言語のチュートリアルをご覧ください。API の詳細については、関連する API リファレンスをご覧ください。

スカラー値型

スカラー メッセージ フィールドには、次のいずれかの型を指定できます。表には、.proto ファイルで指定した型と、自動的に生成されるクラスの対応する型が示されています。

.proto タイプ 備考 C++ 型 Java の型 Python 型[2] 移動のタイプ
倍精度 double 倍精度 float *浮動小数点数 64
float float float float *浮動小数点 32
int32 可変長エンコードを使用します。負の値のエンコードは非効率的 – フィールドに負の値が含まれる可能性がある場合は、代わりに sint32 を使用します。 int32 int int *int32
int64 可変長エンコードを使用します。負の数値をエンコードする効率が悪い - フィールドに負の値が含まれる可能性がある場合は、代わりに sint64 を使用します。 int64 long 整数/long[3] *int64
uint32 可変長エンコードを使用します。 uint32 整数[1] 整数 / long[3] *uint32
uint64 可変長エンコードを使用します。 uint64 長め[1] 整数/long[3] *uint64
Sint32 可変長エンコードを使用します。符号付き整数値。これらは、通常の int32 よりも効率的に負の数をエンコードします。 int32 int int *int32
Sint64 可変長エンコードを使用します。符号付き整数値。これらは、通常の int64 よりも効率的に負の数をエンコードします。 int64 long 整数/long[3] *int64
固定 32 常に 4 バイトです。値が 228 より大きい場合、uint32 よりも効率的です。 uint32 整数[1] 整数/long[3] *uint32
固定 64 常に 8 バイトです。値が 256 より大きい場合、uint64 よりも効率的です。 uint64 長め[1] 整数/long[3] *uint64
修正済み 32 常に 4 バイトです。 int32 int int *int32
修正済み 64 常に 8 バイトです。 int64 long 整数/long[3] *int64
ブール値 ブール値 boolean ブール値 *ブール値
文字列 文字列には常に UTF-8 エンコード テキストを含める必要があります。 文字列 String unicode(Python 2)または str(Python 3) *文字列
バイト 任意のバイト シーケンスを含めることができます。 文字列 ByteString バイト []バイト

メッセージをシリアル化するときにこれらの型がエンコードされる方法について詳しくは、プロトコル バッファ エンコードをご覧ください。

[1] Java では、32 ビットと 64 ビットの符号なし整数は符号付きの符号を使用して表され、最上位のビットは単に符号ビットに格納されます。

[2] いずれの場合も、フィールドに値を設定すると、型が有効かどうかチェックされます。

[3] 64 ビット整数または符号なし 32 ビット整数は、デコード時には常に長い整数として表現されますが、フィールドを設定するときに int が指定された場合、int 型にすることができます。いずれの場合も、値は設定時に表される型に収まる必要があります。[2] をご覧ください。

省略可能なフィールドとデフォルト値

前述のように、メッセージの説明の要素には optional ラベルを付けることができます。 メッセージの形式が正しい場合、省略可能な要素が含まれているとは限りません。メッセージが解析される際に省略可能な要素が含まれていない場合、解析されたオブジェクトの対応するフィールドにアクセスすると、そのフィールドのデフォルト値が返されます。デフォルト値は、メッセージの説明の一部として指定できます。 たとえば、SearchRequestresult_per_page 値をデフォルト値の 10 に指定するとします。

optional int32 result_per_page = 3 [default = 10];

省略可能な要素にデフォルト値が指定されていない場合は、型固有のデフォルト値が使用されます。文字列の場合、デフォルト値は空の文字列です。バイトの場合、デフォルト値は空のバイト文字列です。ブール値の場合、デフォルト値は false です。数値型の場合、デフォルト値は 0 です。列挙型の場合、デフォルト値は列挙型の型リストにある最初の値です。つまり、列挙値のリストの先頭に値を追加する際には注意が必要です。定義を安全に変更する方法のガイドラインについては、メッセージ タイプを更新するをご覧ください。

列挙型

メッセージ タイプを定義するときに、そのフィールドのいずれかで、事前定義された値のリストのいずれか 1 つのみが含まれるようにできます。たとえば、SearchRequest ごとに corpus フィールドを追加するとします。この場合、コーパスは UNIVERSALWEBIMAGESLOCALNEWSPRODUCTSVIDEO のいずれかです。これを行うには、メッセージ定義に enum を追加します。enum タイプのフィールドには、指定された定数セットの 1 つのみを値として指定できます(別の値を指定しようとすると、パーサーは不明なフィールドのように扱われます)。次の例では、考えられるすべての値と Corpus 型のフィールドを持つ Corpus という enum を追加しています。

enum Corpus {
  CORPUS_UNSPECIFIED = 0;
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3 [default = 10];
  optional Corpus corpus = 4 [default = CORPUS_UNIVERSAL];
}

エイリアスを定義するには、同じ値を異なる列挙型定数に割り当てます。これを行うには、allow_alias オプションを true に設定する必要があります。そうでない場合、エイリアスが見つかると、プロトコル バッファ コンパイラでエラー メッセージが生成されます。 シリアル化解除中はエイリアス値がすべて有効ですが、シリアル化時には最初の値が常に使用されます。

enum EnumAllowingAlias {
  option allow_alias = true;
  EAA_UNSPECIFIED = 0;
  EAA_STARTED = 1;
  EAA_RUNNING = 1;
  EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
  ENAA_UNSPECIFIED = 0;
  ENAA_STARTED = 1;
  // ENAA_RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
  ENAA_FINISHED = 2;
}

列挙定数は 32 ビット整数の範囲内になければなりません。enum 値はこの接続で変数エンコードを使用するため、負の値は非効率的で、推奨されません。enum は、メッセージ定義の内部または外部で定義できます。これらの enum は、.proto ファイルの任意のメッセージ定義で再利用できます。また、_MessageType_._EnumType_ 構文を使用して、あるメッセージで宣言されている enum 型を別のメッセージ内のフィールドの型として使用することもできます。

enum を使用する .proto でプロトコル バッファ コンパイラを実行すると、生成されたコードには対応する Java または C++ の enum か、ランタイムに生成されるクラスに整数値を持つシンボリック定数のセットを作成するために使用される特別な EnumDescriptor クラスがあります。

アプリでメッセージ enum を操作する方法の詳細については、選択した言語の生成コードガイドをご覧ください。

予約済みの値

列挙型エントリを完全に削除するかコメントアウトすることで列挙型を更新した場合、将来のユーザーは、型に独自の更新を行う際に数値を再利用できます。これにより、後でデータの破損やプライバシー バグなど、同じ.protoの古いバージョンを読み込むと、重大な問題が発生することがあります。これを回避する方法の一つは、削除されたエントリの数値(および JSON シリアル化の問題を引き起こす可能性がある名前)を reserved に指定することです。今後のユーザーがこれらの識別子を使用しようとすると、プロトコル バッファ コンパイラはエラーになります。max キーワードを使用して、予約された数値の範囲を最大値として指定できます。


enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

同じ reserved ステートメント内でフィールド名と数値を組み合わせることはできません。

他のメッセージ タイプの使用

他のメッセージ タイプをフィールド タイプとして使用できます。たとえば、各 SearchResponse メッセージに Result メッセージを含める場合、同じ .protoResult メッセージ タイプを定義し、SearchResponseResult 型のフィールドを指定します。


message SearchResponse {
  repeated Result result = 1;
}

message Result {
  required string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
}

定義のインポート

上記の例では、Result メッセージ タイプは SearchResponse と同じファイルで定義されています。フィールド タイプとして使用するメッセージ タイプが別の .proto ファイルですでに定義されている場合はどうなるでしょうか。

他の .proto ファイルの定義を使用するには、ファイルをインポートします。別の .proto の定義をインポートするには、ファイルの先頭に import ステートメントを追加します。

import "myproject/other_protos.proto";

デフォルトでは、直接インポートされた .proto ファイルからの定義のみを使用できます。 ただし、場合によっては、.proto ファイルを新しい場所に移動する必要があります。.proto ファイルを直接移動して、すべてのコールサイトを一度に変更するのではなく、プレースホルダ .proto ファイルを以前の場所に配置することで、すべてのインポートを import public 概念を使用して新しい場所に転送できます。

注: 公開インポート機能は Java では使用できません。

import public 依存関係は、import public ステートメントを含む proto をインポートするすべてのコードで推移的に使用できます。例:

// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

プロトコル コンパイラは、-I/--proto_path フラグを使用して、プロトコル コンパイラのコマンドラインで指定されたディレクトリ内のインポートされたファイルを検索します。フラグが指定されていない場合、コンパイラが呼び出されたディレクトリを検索します。一般に、--proto_path フラグをプロジェクトのルートに設定し、すべてのインポートで完全修飾名を使用する必要があります。

proto3 メッセージ タイプの使用

proto3 メッセージ タイプをインポートして proto2 メッセージで使用できます。その逆も可能です。ただし、proto2 列挙型は proto3 構文では使用できません。

ネストされた型

他のメッセージ タイプ内でメッセージ タイプを定義して使用できます。次の例では、Result メッセージが SearchResponse メッセージ内に定義されています。

message SearchResponse {
  message Result {
    required string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
  repeated Result result = 1;
}

このメッセージ タイプを親メッセージ タイプの外部で再利用する場合は、_Parent_._Type_ とします。

message SomeOtherMessage {
  optional SearchResponse.Result result = 1;
}

メッセージは、いくつでもネストできます。以下の例では、Inner という名前の 2 つのネスト型は、異なるメッセージ内で定義されているため、完全に独立しています。

message Outer {       // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      optional int64 ival = 1;
      optional bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      optional string name = 1;
      optional bool   flag = 2;
    }
  }
}

グループ

グループ機能は非推奨になっているため、新しいメッセージ タイプを作成するときには使用しないでください。代わりに、ネストされたメッセージ タイプを使用してください。

グループは、メッセージ定義に情報をネストするもう一つの方法です。たとえば、次のように複数の Result を含む SearchResponse を指定する別の方法を以下に示します。

message SearchResponse {
  repeated group Result = 1 {
    required string url = 2;
    optional string title = 3;
    repeated string snippets = 4;
  }
}

グループは、ネストされたメッセージ タイプとフィールドを単純に 1 つの宣言にまとめます。コードでは、このメッセージを result という Result 型フィールドがあるかのように扱うことができます(後者は、前者と競合しないように、後者の名前を小文字に変換します)。そのため、この例は上記の SearchResponse とまったく同じですが、メッセージの転送形式が異なります。

メッセージ タイプの更新

既存のメッセージ タイプですべてのニーズを満たせない場合や(メッセージ形式に追加フィールドがある場合でも)は、古い形式で作成したコードを使用することも可能ですのでご安心ください。バイナリコード形式を使用すると、既存のコードを壊すことなくメッセージ タイプを簡単に更新できます。

バイナリ転送形式を使用している場合は、次のルールを確認してください。

  • 既存のフィールドのフィールド番号は変更しないでください。
  • 新しく追加するフィールドはすべて optional または repeated にする必要があります。つまり、「古い」メッセージ形式を使用してコードでシリアル化されたメッセージは、required 要素が一切なくなるため、新しく生成されたコードで解析できます。新しいコードで古いコードによって生成されたメッセージを適切に操作できるように、これらの要素には適切なデフォルト値を設定する必要があります。同様に、新しいコードによって作成されたメッセージは、古いコードで解析できます。古いバイナリは、解析時に新しいフィールドを無視するだけです。ただし、不明なフィールドは破棄されません。後でメッセージがシリアル化された場合、不明なフィールドはシリアル化されてシリアル化されます。したがって、メッセージが新しいコードに渡されても、新しいフィールドを使用できます。
  • 更新されていないメッセージ タイプでフィールド番号が再度使用されない限り、必須項目は削除できます。今後このフィールドを変更する際は、接頭辞「OBSOLETE_」を追加するか、フィールド番号を予約してください。そうすれば、将来の .proto ユーザーがその番号を誤って再利用することがなくなります。
  • 型と番号が同じである限り、必須フィールドは「内線」に変換できます。その逆も同様です。
  • int32uint32int64uint64bool はすべて互換性があります。つまり、上位互換性や下位互換性を損なうことなく、これらのタイプのいずれかからフィールドを変更できます。対応する型に当てはまらないワイヤーから数値が解析される場合、その型を C++ でその型にキャストした場合と同じ効果が得られます(たとえば、64 ビットの数値が int32 として読み取られた場合、32 ビットに切り捨てられます)。
  • sint32sint64 は互いに互換性がありますが、他の整数型と互換性はありません
  • stringbytes は、バイトが有効な UTF-8 である限り、互換性があります。
  • 埋め込みメッセージには、バイト化されているメッセージ バージョンがバイトに含まれている場合に bytes との互換性が確保されます。
  • fixed32sfixed32 と互換性があり、fixed64sfixed64 と互換性があります。
  • stringbytes、メッセージ フィールドの場合、optionalrepeated と互換性があります。繰り返しフィールドの入力シリアル化データを入力として想定する場合、このフィールドがプリミティブ型フィールドの場合は optional が最後の入力値を取得し、メッセージ型フィールドの場合はすべての入力要素をマージします。通常、ブール値や列挙型などの数値型には安全ではありません。数値タイプの繰り返しフィールドは、pack 形式でシリアル化できます。これは、optional フィールドが想定されていたときに正しく解析されないことを意味します。
  • デフォルト値が変更されてネットワーク経由で送信されない限り、通常は問題ありません。したがって、特定のフィールドが設定されていない場合に、そのメッセージを受信したプログラムは、プログラムのそのプロトコル バージョンで定義されているとおりにデフォルト値を表示します。 送信者のコードで定義されたデフォルト値は表示されません。
  • enum は、ワイヤ形式の点で int32uint32int64uint64 と互換性があります(0 と収まらない場合は、値が切り捨てられます)。ただし、メッセージがシリアル化解除された場合、クライアント コードでそれらの値の処理方法が異なることに注意してください。不明な enum 値は、メッセージがシリアル化解除されると破棄されます。これにより、フィールドの has.. アクセサーが false を返し、ゲッターが enum 定義にリストされている最初の値(指定されている場合はデフォルト値)を返すようになります。列挙型フィールドが繰り返される場合、認識されない値はリストから除去されます。ただし、整数フィールドは常にその値を保持します。そのため、ワイヤで境界外の列挙値を受信するという点で、整数を enum にアップグレードする際は注意が必要です。
  • 現在の Java と C++ の実装では、認識されない enum 値が取り除かれると、他の不明なフィールドとともに保存されます。このデータがシリアル化され、これらの値を認識するクライアントによって再解析されると、奇妙な動作が発生する可能性があります。オプション フィールドの場合、元のメッセージがシリアル化解除された後に新しい値が書き込まれた場合でも、それを認識するクライアントによって古い値が読み取られます。繰り返しフィールドの場合、認識された値と新しく追加された値の後に古い値が表示されるため、順序は保持されません。
  • 単一の optional フィールドまたは拡張機能を新しい oneof のメンバーに変更することはバイナリと互換性がありますが、一部の言語(特に Go)では、生成されたコードの API が互換性のない方法で変更されます。このため、AIP-180 に記載されているとおり、Google は公開 API でそのような変更を行いません。ソースの互換性に関する注意点と同様に、複数のフィールドを新しい oneof に移動することは、一度に複数のコードを設定しないことが確実であれば、安全です。既存の oneof にフィールドを移動するのは安全ではありません。同様に、単一のフィールド oneofoptional フィールドまたは拡張機能に変更しても安全です。
  • map<K, V> とそれに対応する repeated メッセージ フィールドの間のフィールドの変更はバイナリ互換です(メッセージ レイアウトとその他の制限については、下記のマップをご覧ください)。ただし、この変更の安全性はアプリケーションによって異なります。メッセージを逆シリアル化または再シリアル化する場合、repeated フィールド定義を使用するクライアントは、意味的に同じ結果を生成します。ただし、map フィールド定義を使用するクライアントは、エントリを並べ替え、重複するキーを持つエントリをドロップする場合があります。

広告表示オプション

拡張機能を使用すると、メッセージ内のフィールド番号の範囲がサードパーティの拡張機能で利用可能であることを宣言できます。拡張は、元の .proto ファイルで定義されていないフィールドのプレースホルダです。これにより、一部のフィールドまたはすべてのフィールドの型を、これらのフィールド番号で定義することにより、他の .proto ファイルをメッセージ定義に追加できます。例を見てみましょう。

message Foo {
  // ...
  extensions 100 to 199;
}

これは、Foo のフィールド番号範囲 [100, 199] が拡張機能用に予約されていることを示しています。他のユーザーが、指定した範囲内のフィールド番号を使用して、.proto をインポートする独自の .proto ファイルの Foo に新しいフィールドを追加できるようになりました。次に例を示します。

extend Foo {
  optional int32 bar = 126;
}

これにより、フィールド番号 126 の bar というフィールドが Foo の元の定義に追加されます。

ユーザーの Foo メッセージがエンコードされる場合、ワイヤ形式は、ユーザーが Foo 内で新しいフィールドを定義した場合とまったく同じになります。ただし、アプリケーション コードで拡張フィールドにアクセスする方法は、通常のフィールドにアクセスする方法と少し異なります。生成されたデータアクセス コードには、拡張機能を操作するための特別なアクセサがあります。たとえば、C++ で bar の値を設定するには、次のようにします。

Foo foo;
foo.SetExtension(bar, 15);

同様に、Foo クラスは、テンプレート化されたアクセサーの HasExtension()ClearExtension()GetExtension()MutableExtension()AddExtension() を定義します。どの関数にも、通常のフィールドに対応する生成されたアクセサーと一致するセマンティクスがあります。拡張機能の操作について詳しくは、選択した言語用に生成されたコード リファレンスをご覧ください。

拡張機能は、メッセージ型を含む任意のフィールド型にできますが、oneofs や map は使用できません。

ネストされた拡張機能

別のタイプのスコープで拡張機能を宣言できます。

message Baz {
  extend Foo {
    optional int32 bar = 126;
  }
  ...
}

この場合、この拡張機能にアクセスするための C++ コードは次のようになります。

Foo foo;
foo.SetExtension(Baz::bar, 15);

つまり、barBaz のスコープ内で定義される唯一の効果があります。

これはよく混同の原因となります。メッセージ タイプ内にネストされている extend ブロックを宣言しても、外部型と拡張型の間に関係はありません。特に、上記の例は、BazFoo のサブクラスであることを意味するものではありません。つまり、シンボル barBaz のスコープ内で宣言されているだけで、単なる静的メンバーです。

一般的なパターンでは、拡張機能のフィールド タイプのスコープ内で拡張機能を定義します。たとえば、Baz 型の Foo への拡張機能は次のようになります。これは、拡張機能が Baz の一部として定義されます。

message Baz {
  extend Foo {
    optional Baz foo_ext = 127;
  }
  ...
}

ただし、メッセージ タイプの拡張機能をそのタイプ内に定義する必要はありません。以下のようにすることもできます。

message Baz {
  ...
}

// This can even be in a different file.
extend Foo {
  optional Baz foo_baz_ext = 127;
}

混乱を避けるために、この構文を使用することを推奨します。前述のように、ネストされた構文は、多くの場合、拡張機能に慣れていないユーザーによるサブクラス化と間違えられます。

内線番号の選択

2 つのユーザーが同じフィールド番号を使用して同じメッセージ型に拡張機能を追加しないようにすることが重要です。拡張機能が間違った型として誤って解釈されると、データが破損する可能性があります。これを回避するには、プロジェクトの内線番号規則を定義することをおすすめします。

番号付け規則でフィールド番号が非常に大きい拡張を含む場合は、max キーワードを使用して拡張範囲を可能な限り最大のフィールド番号まで指定できます。

message Foo {
  extensions 1000 to max;
}

max は 229 - 1 または 536,870,911 です。

一般的にフィールド番号を選択する場合、番号付け規則ではプロトコル バッファの実装用に予約されているため、フィールド番号 19000 ~ 19999(FieldDescriptor::kFirstReservedNumberFieldDescriptor::kLastReservedNumber)も回避する必要があります。この範囲を含む拡張範囲を定義できますが、プロトコル コンパイラでは、これらの番号を使用して実際の拡張を定義することはできません。

オネオ

メッセージに多くのオプション フィールドがあり、最大 1 つのフィールドが同時に設定される場合は、oneor 機能を使用してこの動作を強制し、メモリを節約できます。

1 つのフィールドはオプション フィールドに似ていますが、1 つの共有メモリ内のすべてのフィールドを同時に設定できます。最大 1 つのフィールドを同時に設定できます。いずれかのメンバーを設定すると、他のすべてのメンバーは自動的に消去されます。選択した言語に応じて、特別な case() メソッドまたは WhichOneof() メソッドを使用して、いずれかの値が設定されます(存在する場合)。

Oneof の使用

.proto で oneof を定義するには、oneof キーワードに続いて oneof 名(この場合は test_oneof)を使用します。

message SampleMessage {
  oneof test_oneof {
     string name = 4;
     SubMessage sub_message = 9;
  }
}

次に、oneof 定義に oneof フィールドを追加します。任意のタイプのフィールドを追加できますが、requiredoptionalrepeated キーワードは使用できません。いずれかに繰り返しフィールドを追加する必要がある場合は、繰り返しフィールドを含むメッセージを使用できます。

生成されたコードでは、ゲッターとセッターが通常の optional メソッドと同じフィールドを持ちます。また、いずれかに設定されている値(存在する場合)を確認するための特別なメソッドも取得できます。選択した言語に対応する API の詳細については、関連する API リファレンスをご覧ください。

Oneof 機能

  • oneof フィールドを設定すると、oneof の他のすべてのメンバーが自動的に消去されます。複数のフィールドを設定しても、最後に設定したフィールドにのみ値が格納されます。

    SampleMessage message;
    message.set_name("name");
    CHECK(message.has_name());
    message.mutable_sub_message();   // Will clear name field.
    CHECK(!message.has_name());
    
  • パーサーがケーブルの同じメンバーを複数検出した場合、最後に確認されたメンバーのみが解析済みメッセージで使用されます。

  • どの拡張機能もサポートされていません。

  • いずれか 1 つを repeated にすることはできません。

  • リフレクション API はいずれかのフィールドで機能します。

  • oneof フィールドをデフォルト値に設定する(int32 oneof フィールドを 0 に設定するなど)と、その oneof フィールドの「ケース」が設定され、値がネットワーク上でシリアル化されます。

  • C++ を使用している場合は、コードでメモリ クラッシュが発生していないことを確認してください。次のサンプルコードは、set_name() メソッドを呼び出して sub_message がすでに削除されているため、クラッシュします。

    SampleMessage message;
    SubMessage* sub_message = message.mutable_sub_message();
    message.set_name("name");      // Will delete sub_message
    sub_message->set_...            // Crashes here
    
  • C++ でも、ofof を持つ 2 つのメッセージに Swap() を指定すると、各メッセージはどちらか一方のケースになります。次の例では、msg1 には sub_message が含まれ、msg2 には name があります。

    SampleMessage msg1;
    msg1.set_name("name");
    SampleMessage msg2;
    msg2.mutable_sub_message();
    msg1.swap(&msg2);
    CHECK(msg1.has_sub_message());
    CHECK(msg2.has_name());
    

下位互換性の問題

フィールドを追加または削除する場合は、注意が必要です。oneof の値の確認で None または NOT_SET が返される場合は、oneof が設定されていないか、oneof の別のバージョンのフィールドに設定されている可能性があります。ワイヤ上の未知のフィールドがそのフィールドのものかどうかを特定する方法がないため、違いを見分ける方法はありません。

タグの再利用に関する問題

  • オプション フィールドを次のいずれかに移動: メッセージのシリアル化と解析が行われた後に、一部の情報が失われることがあります(一部のフィールドは消去されます)。ただし、1 つのフィールドを新しいフィールドに安全に移動できます。また、1 つしか設定されていないことがわかっている場合は、複数のフィールドを移動できます。詳細については、メッセージ タイプを更新するをご覧ください。
  • oneof フィールドを削除して追加: メッセージのシリアル化と解析が行われた後に、現在設定されている oneof フィールドが削除される場合があります。
  • いずれかを分割または結合する: 通常の optional フィールドを移動する場合と同様の問題があります。

マップ

データ定義の一部として関連付けマップを作成する場合は、プロトコル バッファに便利なショートカット構文が用意されています。

map<key_type, value_type> map_field = N;

ここで、key_type は任意の整数型または文字列型(つまり、浮動小数点型と bytes を除くスカラー型)にすることができます。列挙型は有効な key_type ではないことに注意してください。value_type には、別のマップを除くすべてのタイプを指定できます。

たとえば、各 Project メッセージが文字列キーに関連付けられているプロジェクトのマップを作成する場合は、次のように定義します。

map<string, Project> projects = 3;

生成されたマップ API は現在、proto2 でサポートされているすべての言語で利用できます。選択した言語の Map API について詳しくは、関連する API リファレンスをご覧ください。

マップの機能

  • 拡張機能はマップではサポートされていません。
  • マップを repeatedoptionalrequired にすることはできません。
  • マップ値のワイヤ形式の順序とマップの反復順序は定義されていないため、マップ アイテムが特定の順序にあるとは限りません。
  • .proto のテキスト形式を生成する場合、マップはキーで並べ替えられます。数値キーは数値で並べ替えられます。
  • ケーブルから解析するときやマージするときに、マップキーが重複している場合は最後に使用されたキーが使用されます。テキスト形式からマップを解析するときに、キーが重複すると解析が失敗することがあります。

下位互換性

マップ構文は接続例と同等であるため、マップをサポートしないプロトコル バッファ実装はデータを処理できます。

message MapFieldEntry {
  optional key_type key = 1;
  optional value_type value = 2;
}

repeated MapFieldEntry map_field = N;

地図をサポートするプロトコル バッファの実装では、上記の定義で受け入れられるデータを生成して受け入れる必要があります。

パッケージ

オプションの package 指定子を .proto ファイルに追加して、プロトコル メッセージ タイプ間の名前の競合を防ぐことができます。

package foo.bar;
message Open { ... }

その後、メッセージ型のフィールドを定義するときにパッケージ指定子を使用できます。

message Foo {
  ...
  required foo.bar.Open open = 1;
  ...
}

パッケージ指定子が生成されるコードに与える影響は、選択した言語によって異なります。

  • C++ では、生成されたクラスは C++ 名前空間にラップされます。たとえば、Open は名前空間 foo::bar にあります。
  • Java では、.proto ファイルに option java_package を明示的に指定しない限り、パッケージは Java パッケージとして使用されます。
  • Python では、Python モジュールはファイル システム内の位置に応じて編成されるため、package ディレクティブは無視されます。
  • Go では、package ディレクティブは無視され、生成された .pb.go ファイルは、対応する go_proto_library ルールの名前が付いたパッケージに格納されます。

Python などで package ディレクティブが生成されたコードに直接影響しない場合でも、.proto ファイルにパッケージを指定することを強くおすすめします。そうしないと、記述子で名前の競合が生じたり、proto が他の言語で移植できなくなる可能性があります。

パッケージと名前解決

プロトコル バッファ言語の型の名前解決は C++ のような働きをします。まず最も内側のスコープが検索され、次に次に最も内側のスコープが検索され、以降、同じように親パッケージの「内部」とみなされます。先頭の「.」(例: .foo.bar.Baz)は、代わりに最も外側のスコープから開始することを意味します。

プロトコル バッファ コンパイラは、インポートされた .proto ファイルを解析することですべての型名を解決します。言語ごとのジェネレータは、スコープルールが異なる場合でも、その言語で各タイプを参照する方法を認識します。

サービスの定義

RPC(Remote Procedure Call)システムでメッセージ タイプを使用する場合は、.proto ファイルで RPC サービス インターフェースを定義すると、プロトコル バッファ コンパイラが選択した言語でサービス インターフェース コードとスタブを生成します。たとえば、SearchRequest を受け取って SearchResponse を返すメソッドで RPC サービスを定義する場合は、.proto ファイルで次のように定義できます。

service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

デフォルトでは、プロトコル コンパイラは SearchService という抽象インターフェースと、対応する「スタブ」実装を生成します。スタブはすべての呼び出しを RpcChannel に転送します。これはさらに、独自の RPC システムで自身を定義する必要がある抽象インターフェースです。たとえば、RpcChannel を実装して、メッセージをシリアル化し、HTTP 経由でサーバーに送信します。つまり、生成されるスタブは、特定の RPC 実装にロックインすることなく、プロトコル バッファベースの RPC 呼び出しを行うためのタイプセーフなインターフェースを提供します。そのため、C++ では次のようなコードになります。

using google::protobuf;

protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;

void DoSearch() {
  // You provide classes MyRpcChannel and MyRpcController, which implement
  // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
  channel = new MyRpcChannel("somehost.example.com:1234");
  controller = new MyRpcController;

  // The protocol compiler generates the SearchService class based on the
  // definition given above.
  service = new SearchService::Stub(channel);

  // Set up the request.
  request.set_query("protocol buffers");

  // Execute the RPC.
  service->Search(controller, &request, &response,
                  protobuf::NewCallback(&Done));
}

void Done() {
  delete service;
  delete channel;
  delete controller;
}

すべてのサービスクラスには、Service インターフェースも実装されています。これにより、コンパイル時にメソッド名またはその入出力型を認識せずに特定のメソッドを呼び出すことができます。サーバー側では、これを使用してサービスを登録できる RPC サーバーを実装できます。

using google::protobuf;

class ExampleSearchService : public SearchService {
 public:
  void Search(protobuf::RpcController* controller,
              const SearchRequest* request,
              SearchResponse* response,
              protobuf::Closure* done) {
    if (request->query() == "google") {
      response->add_result()->set_url("http://www.google.com");
    } else if (request->query() == "protocol buffers") {
      response->add_result()->set_url("http://protobuf.googlecode.com");
    }
    done->Run();
  }
};

int main() {
  // You provide class MyRpcServer.  It does not have to implement any
  // particular interface; this is just an example.
  MyRpcServer server;

  protobuf::Service* service = new ExampleSearchService;
  server.ExportOnPort(1234, service);
  server.Run();

  delete service;
  return 0;
}

独自の既存の RPC システムを接続しない場合、gRPC を使用できるようになりました。これは、Google で開発された、言語とプラットフォームに中立なオープンソース RPC システムです。gRPC は、特にプロトコル バッファで適切に動作し、特別なプロトコル バッファ コンパイラ プラグインを使用して .proto ファイルから直接、関連する RPC コードを生成できます。ただし、proto2 と proto3 で生成されたクライアントとサーバーの間には互換性の問題があるため、gRPC サービスを定義するには proto3 を使用することをおすすめします。proto3 の構文について詳しくは、Proto3 言語ガイドをご覧ください。gRPC で proto2 を使用する場合は、バージョン 3.0.0 以降のプロトコル バッファ コンパイラとライブラリを使用する必要があります。

gRPC 以外にも、プロトコル バッファ用の RPC 実装を開発するためのサードパーティ プロジェクトが多数あります。既知のプロジェクトへのリンクの一覧については、サードパーティのアドオン Wiki ページをご覧ください。

オプション

.proto ファイル内の個々の宣言には、複数のオプションをアノテーションできます。オプションにより宣言の全体的な意味は変わりませんが、特定のコンテキストでの処理方法に影響する可能性があります。使用可能なオプションの一覧については、/google/protobuf/descriptor.proto をご覧ください。

一部のオプションはファイルレベルのオプションです。つまり、メッセージ、列挙型、サービス定義の内部ではなく、最上位のスコープで記述する必要があります。一部のオプションはメッセージ レベルのオプションであり、メッセージ定義内に記述する必要があります。一部のオプションはフィールド レベルのオプションであり、フィールド定義内に記述する必要があります。オプションは、列挙型、列挙値、フィールドの 1 つ、サービスタイプ、サービス メソッドで記述することもできますが、いずれのオプションも現在のところ使用できません。

よく使用されるオプションをいくつか紹介します。

  • java_package(ファイル オプション): 生成された Java クラスに使用するパッケージ。.proto ファイルに明示的な java_package オプションが指定されていない場合、デフォルトで proto パッケージ(.proto ファイルの「package」キーワードを使用して指定)が使用されます。ただし、proto パッケージは逆方向のドメイン名で始まることが想定されていないため、一般的に proto パッケージは適切な Java パッケージとして機能しません。Java コードを生成しない場合、このオプションは無効になります。

    option java_package = "com.example.foo";
    
  • java_outer_classname(ファイル オプション): 生成するラッパー Java クラスのクラス名(したがってファイル名)。.proto ファイルで明示的な java_outer_classname が指定されていない場合、.proto ファイル名をキャメルケースに変換することでクラス名が作成されます(foo_bar.protoFooBar.java になります)。java_multiple_files オプションが無効になっている場合、.proto ファイルに対して生成された他のクラス / 列挙型などはすべて、この外部ラッパー Java クラスをネストされたクラス / 列挙型などとして生成します。Java コードを生成しない場合、この効果はありません。

    option java_outer_classname = "Ponycopter";
    
  • java_multiple_files(ファイル オプション): false の場合、この .proto ファイルに対して生成される .java ファイルは 1 つだけであり、トップレベル メッセージ、サービス、列挙型について生成されるすべての Java クラス / 列挙型などは、外部クラス内にネストされます(java_outer_classname を参照)。true の場合、Java クラス / 列挙型などでグループ化された最大クラス / 列挙型のメッセージがJava コードを生成しない場合、このオプションは無効になります。

    option java_multiple_files = true;
    
  • optimize_for(ファイル オプション): SPEEDCODE_SIZELITE_RUNTIME のいずれかに設定できます。C++ と Java のコード生成ツール(場合によってはサードパーティの生成ツール)には、次の影響があります。

    • SPEED(デフォルト): プロトコル バッファ コンパイラは、メッセージ タイプに対するシリアル化、一般的な解析、その他の一般的なオペレーションのためのコードを生成します。このコードは高度に最適化されています。
    • CODE_SIZE: プロトコル バッファ コンパイラは最小限のクラスを生成し、リフレクション ベースの共有コードに依存してシリアライゼーション、解析、その他のさまざまなオペレーションを実装します。したがって、生成されるコードは SPEED よりもはるかに少なくなりますが、オペレーションは遅くなります。クラスは、SPEED モードとまったく同じ公開 API を実装します。このモードは、非常に多くの .proto ファイルが含まれ、それらすべてを高速に処理できるわけではないアプリで非常に便利です。
    • LITE_RUNTIME: プロトコル バッファ コンパイラは、「lite」ランタイム ライブラリのみに依存するクラスを生成します(libprotobuf ではなく libprotobuf-lite)。Lite ランタイムはライブラリ全体よりもはるかに小さくなります(約 1 桁小さくなります)。ただし、記述子やリフレクションなどの特定の機能は省略されます。これは、スマートフォンなど、制約のあるプラットフォームで実行されるアプリに対して特に便利です。コンパイラは、SPEED モードの場合と同様に、すべてのメソッドの高速実装を生成します。 生成されるクラスは各言語の MessageLite インターフェースのみを実装します。これにより、完全な Message インターフェースのメソッドのサブセットのみが提供されます。
    option optimize_for = CODE_SIZE;
    
  • cc_generic_servicesjava_generic_servicespy_generic_services(ファイル オプション): プロトコル バッファ コンパイラが、それぞれ C++、Java、Python のサービス定義に基づいて抽象サービスコードを生成するかどうかを指定します。以前の理由から、デフォルトは true です。ただし、バージョン 2.3.0(2010 年 1 月)以降は、抽象サービスに依存するのではなく、RPC の実装で、各システムに固有のコードを生成するコード生成プラグインを提供することが推奨されています。

    // This file relies on plugins to generate service code.
    option cc_generic_services = false;
    option java_generic_services = false;
    option py_generic_services = false;
    
  • cc_enable_arenas(ファイル オプション): C++ で生成されたコードのアリーナ割り当てを有効にします。

  • message_set_wire_format(メッセージ オプション): true に設定すると、Google 内部で使用されている古い形式との互換性を確保するために、MessageSet という別のバイナリ形式がメッセージで使用されます。Google の外部のユーザーは、このオプションを使用する必要はおそらくありません。メッセージは、次のように正確に宣言する必要があります。

    message Foo {
      option message_set_wire_format = true;
      extensions 4 to max;
    }
    
  • packed(フィールド オプション): 基本的な数値型の繰り返しフィールドで true に設定すると、よりコンパクトなエンコードが使用されます。このオプションには欠点はありません。ただし、バージョン 2.3.0 より前のバージョンでは、想定されていないときにパックされたデータを受け取るパーサーはこれを無視することに注意してください。そのため、既存のフィールドをパック形式に変更することはできません。転送時の互換性も損なわれませんでした。2.3.0 以降では、この変更は安全です。圧縮可能なフィールドのパーサーは常に両方の形式を受け入れるので、古いバージョンの protobuf バージョンを使用する古いプログラムを処理する必要がある場合には注意が必要です。

    repeated int32 samples = 4 [packed = true];
    
  • deprecated(フィールド オプション): true に設定すると、このフィールドは非推奨になり、新しいコードで使用されなくなります。ほとんどの言語では、実際に影響はありません。Java では、これは @Deprecated アノテーションになります。C++ では、clang-tidy を使用すると、サポートが終了したフィールドが使用されるたびに警告が生成されます。将来的には、他の言語固有のコード生成ツールがフィールドのアクセサで非推奨アノテーションを生成する可能性があります。これにより、フィールドを使用するコードをコンパイルする際に警告が発せられます。このフィールドが誰も使用しておらず、新しいユーザーがフィールドを使用できないようにする場合は、フィールド宣言を予約済みステートメントに置き換えることを検討してください。

    optional int32 old_field = 6 [deprecated=true];
    

カスタマイズオプション

プロトコル バッファでは、独自のオプションを定義して使用することもできます。これは、ほとんどのユーザーにとって不要な高度な機能です。オプションは google/protobuf/descriptor.proto で定義されたメッセージ(FileOptionsFieldOptions など)によって定義されるため、独自のオプションを定義するには、単にそれらのメッセージを拡張する必要があります。例:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}

message MyMessage {
  option (my_option) = "Hello world!";
}

ここでは、MessageOptions を拡張することにより、新しいメッセージ レベルのオプションを定義しました。このオプションを使用するときは、オプション名であることを示すために丸かっこで囲む必要があります。これで、次のように C++ で my_option の値を読み取れるようになりました。

string value = MyMessage::descriptor()->options().GetExtension(my_option);

ここで、MyMessage::descriptor()->options()MyMessageMessageOptions プロトコル メッセージを返します。そこからカスタム オプションを読み取る方法は、他の拡張機能を読み取る方法と同じです。

同様に、Java では次のように記述します。

String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
  .getExtension(MyProtoFile.myOption);

Python では、次のようになります。

value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
  .Extensions[my_proto_file_pb2.my_option]

カスタム オプションは、Protocol Buffers の言語で、あらゆる種類の構成要素に定義できます。あらゆる種類のオプションを使用した例を以下に示します。

import "google/protobuf/descriptor.proto";

extend google.protobuf.FileOptions {
  optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
  optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
  optional float my_field_option = 50002;
}
extend google.protobuf.OneofOptions {
  optional int64 my_oneof_option = 50003;
}
extend google.protobuf.EnumOptions {
  optional bool my_enum_option = 50004;
}
extend google.protobuf.EnumValueOptions {
  optional uint32 my_enum_value_option = 50005;
}
extend google.protobuf.ServiceOptions {
  optional MyEnum my_service_option = 50006;
}
extend google.protobuf.MethodOptions {
  optional MyMessage my_method_option = 50007;
}

option (my_file_option) = "Hello world!";

message MyMessage {
  option (my_message_option) = 1234;

  optional int32 foo = 1 [(my_field_option) = 4.5];
  optional string bar = 2;
  oneof qux {
    option (my_oneof_option) = 42;

    string quux = 3;
  }
}

enum MyEnum {
  option (my_enum_option) = true;

  FOO = 1 [(my_enum_value_option) = 321];
  BAR = 2;
}

message RequestType {}
message ResponseType {}

service MyService {
  option (my_service_option) = FOO;

  rpc MyMethod(RequestType) returns(ResponseType) {
    // Note:  my_method_option has type MyMessage.  We can set each field
    //   within it using a separate "option" line.
    option (my_method_option).foo = 567;
    option (my_method_option).bar = "Some string";
  }
}

カスタム オプションが定義されているパッケージ以外のカスタム オプションをパッケージで使用する場合は、タイプ名の場合と同様に、オプション名の前にパッケージ名を付ける必要があります。例:

// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
  option (foo.my_option) = "Hello world!";
}

最後に、カスタム オプションは拡張機能であるため、他のフィールドや拡張機能と同様に、フィールド番号を割り当てる必要があります。上の例では、50000 ~ 99999 の範囲のフィールド番号を使用しています。この範囲は個々の組織内で内部使用のために予約されているため、社内アプリケーションにはこの範囲の数値を自由に使用できます。ただし、公開アプリケーションでカスタム オプションを使用する場合は、フィールド番号がグローバルに一意であることを確認することが重要です。グローバルに一意のフィールド番号を取得するには、protobuf グローバル拡張レジストリにエントリを追加するリクエストを送信してください。通常、必要な広告表示オプションは 1 つだけです。複数のオプションを宣言するには、1 つの拡張機能番号をサブメッセージに配置します。

message FooOptions {
  optional int32 opt1 = 1;
  optional string opt2 = 2;
}

extend google.protobuf.FieldOptions {
  optional FooOptions foo_options = 1234;
}

// usage:
message Bar {
  optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
  // alternative aggregate syntax (uses TextFormat):
  optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}

また、各オプション タイプ(ファイルレベル、メッセージ レベル、フィールド レベルなど)には固有の番号スペースがあるため、たとえば、FieldOptions と MessageOptions の拡張機能を同じ番号で宣言できます。

クラスの生成

.proto ファイルで定義されたメッセージ型を操作する必要がある Java、Python、または C++ コードを生成するには、.proto でプロトコル バッファ コンパイラ protoc を実行する必要があります。コンパイラをインストールしていない場合は、パッケージをダウンロードし、README の指示に従ってください。

プロトコル コンパイラは次のように呼び出されます。

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
  • IMPORT_PATH は、import ディレクティブを解決する際に .proto ファイルを探すディレクトリを指定します。省略した場合、現在のディレクトリが使用されます。--proto_path オプションを複数回渡すことで、複数のインポート ディレクトリを指定できます。これらのディレクトリは順番に検索されます。-I=_IMPORT_PATH_ は、--proto_path の短縮形として使用できます。
  • 以下の 1 つ以上の出力ディレクティブを指定できます。

    追加の便宜上、DST_DIR.zip または .jar で終わる場合、コンパイラは出力を、指定された名前の単一の ZIP 形式のアーカイブ ファイルに書き込みます。また、.jar 出力には、Java JAR 仕様で必要とされるマニフェスト ファイルも提供されます。出力アーカイブがすでに存在する場合、そのアーカイブは上書きされます。コンパイラは、既存のアーカイブにファイルを追加できないほどスマートです。

  • 1 つ以上の .proto ファイルを入力として提供する必要があります。一度に複数の .proto ファイルを指定できます。ファイル名は現在のディレクトリからの相対名ですが、コンパイラが正規名を識別できるように、各ファイルは IMPORT_PATH のいずれかに存在している必要があります。

ファイルの場所

他の言語のソースと同じディレクトリに .proto ファイルを配置しないようにします。プロジェクトのルート パッケージの下に、.proto ファイル用のサブパッケージ proto を作成することを検討してください。

地域は言語に依存しない必要がある

Java コードを操作するときに、関連する .proto ファイルを Java ソースと同じディレクトリに置くと便利です。ただし、Java 以外のコードで同じ proto を使用する場合は、パスのプレフィックスは意味をなさなくなります。一般に、proto は、//myteam/mypackage など、言語に依存しない関連するディレクトリに配置します。

このルールの例外は、テストに Java コンテキストでのみ proto が使用されることが明らかである場合です。

サポートされているプラットフォーム

詳細情報: