Dart 生成コード

プロトコルバッファコンパイラが、与えられたプロトコル定義に対してどのようなDartコードを生成するかを説明します。

proto2 と proto3 の生成コードの違いが強調されています。これらの違いは、このドキュメントで説明されている生成コードにあり、両方のバージョンで同じである基本APIにはありません。このドキュメントを読む前に、proto2 言語ガイドおよび/またはproto3 言語ガイドを読むことをお勧めします。

コンパイラの呼び出し

プロトコルバッファコンパイラは、Dartコードを生成するためにプラグインを必要とします。手順に従ってインストールすると、protoc-gen-dart バイナリが提供され、protoc--dart_out コマンドラインフラグを付けて呼び出されたときにこれを使用します。--dart_out フラグは、Dartソースファイルをどこに書き出すかをコンパイラに伝えます。.proto ファイルを入力として与えると、コンパイラは特に .pb.dart ファイルを生成します。

.pb.dart ファイルの名前は、.proto ファイルの名前から2つの変更を加えて計算されます。

  • 拡張子 (.proto) は .pb.dart に置き換えられます。例えば、foo.proto というファイルは、出力ファイルとして foo.pb.dart になります。
  • proto パス (--proto_path または -I コマンドラインフラグで指定) は、出力パス (--dart_out フラグで指定) に置き換えられます。

例えば、コンパイラを次のように呼び出すと、

protoc --proto_path=src --dart_out=build/gen src/foo.proto src/bar/baz.proto

コンパイラは src/foo.protosrc/bar/baz.proto のファイルを読み取ります。そして、build/gen/foo.pb.dartbuild/gen/bar/baz.pb.dart を生成します。コンパイラは必要に応じてディレクトリ build/gen/bar を自動的に作成しますが、build または build/gen作成しません。これらは事前に存在している必要があります。

メッセージ

簡単なメッセージ宣言が与えられた場合、

message Foo {}

プロトコルバッファコンパイラは、GeneratedMessage クラスを拡張する Foo というクラスを生成します。

GeneratedMessage クラスは、メッセージ全体をチェック、操作、読み取り、または書き込みできるメソッドを定義します。これらのメソッドに加えて、Foo クラスは以下のメソッドとコンストラクタを定義します。

  • Foo(): デフォルトコンストラクタ。すべての単一フィールドが未設定で、繰り返しフィールドが空のインスタンスを作成します。
  • Foo.fromBuffer(...): シリアル化されたプロトコルバッファデータから、メッセージを表すFooを作成します。
  • Foo.fromJson(...): メッセージをエンコードしたJSON文字列からFooを作成します。
  • Foo clone(): メッセージ内のフィールドのディープクローンを作成します。
  • Foo copyWith(void Function(Foo) updates): このメッセージの書き込み可能なコピーを作成し、updates を適用し、返す前にコピーを読み取り専用としてマークします。
  • static Foo create(): 単一のFooを作成するファクトリ関数。
  • static PbList<Foo> createRepeated(): Foo 要素の可変繰り返しフィールドを実装するListを作成するファクトリ関数。
  • static Foo getDefault(): Foo のシングルトンインスタンスを返します。これは新しく構築されたFooインスタンスと同一です (つまり、すべての単一フィールドは未設定で、すべての繰り返しフィールドは空です)。

ネストされた型

メッセージは別のメッセージ内に宣言できます。例えば、

message Foo {
  message Bar {
  }
}

この場合、コンパイラは FooFoo_Bar の2つのクラスを生成します。

フィールド

前のセクションで説明したメソッドに加えて、プロトコルバッファコンパイラは、.proto ファイル内のメッセージ内に定義された各フィールドに対してアクセサメソッドを生成します。

生成される名前は常にキャメルケースの命名規則を使用することに注意してください。これは、.proto ファイルのフィールド名がアンダースコア付きの小文字を使用している場合でも同様です (推奨されるように)。ケース変換は次のように機能します。

  1. 名前内の各アンダースコアは削除され、その後の文字が大文字になります。
  2. 名前にプレフィックス(例:「has」)が付く場合、最初の文字は大文字になります。それ以外の場合は小文字になります。

したがって、フィールド foo_bar_baz の場合、ゲッターは get fooBarBaz となり、has で始まるメソッドは hasFooBarBaz となります。

単一プリミティブフィールド (proto2)

これらのいずれかのフィールド定義の場合、

optional int32 foo = 1;
required int32 foo = 1;

コンパイラは、メッセージクラスに以下のアクセサメソッドを生成します。

  • int get foo: フィールドの現在の値を返します。フィールドが設定されていない場合、デフォルト値を返します。
  • bool hasFoo(): フィールドが設定されている場合、true を返します。
  • set foo(int value): フィールドの値を設定します。これを呼び出した後、hasFoo()true を返し、get foovalue を返します。
  • void clearFoo(): フィールドの値をクリアします。これを呼び出した後、hasFoo()false を返し、get foo はデフォルト値を返します。

その他の単純なフィールド型については、スカラー値型テーブルに従って対応するDart型が選択されます。メッセージ型と列挙型については、値の型がメッセージクラスまたは列挙型クラスに置き換えられます。

単一プリミティブフィールド (proto3)

このフィールド定義の場合、

int32 foo = 1;

コンパイラは、メッセージクラスに以下のアクセサメソッドを生成します。

  • int get foo: フィールドの現在の値を返します。フィールドが設定されていない場合、デフォルト値を返します。

  • set foo(int value): フィールドの値を設定します。これを呼び出した後、get foovalue を返します。

  • void clearFoo(): フィールドの値をクリアします。これを呼び出した後、get foo はデフォルト値を返します。

  • bool hasFoo(): フィールドが設定されている場合、true を返します。

  • void clearFoo(): フィールドの値をクリアします。これを呼び出した後、hasFoo()false を返し、get foo はデフォルト値を返します。

単一メッセージフィールド

メッセージ型が与えられた場合、

message Bar {}

Bar フィールドを持つメッセージの場合、

// proto2
message Baz {
  optional Bar bar = 1;
  // The generated code is the same result if required instead of optional.
}

// proto3
message Baz {
  Bar bar = 1;
}

コンパイラは、メッセージクラスに以下のアクセサメソッドを生成します。

  • Bar get bar: フィールドの現在の値を返します。フィールドが設定されていない場合、デフォルト値を返します。
  • set bar(Bar value): フィールドの値を設定します。これを呼び出した後、hasBar()true を返し、get barvalue を返します。
  • bool hasBar(): フィールドが設定されている場合、true を返します。
  • void clearBar(): フィールドの値をクリアします。これを呼び出した後、hasBar()false を返し、get bar はデフォルト値を返します。
  • Bar ensureBar(): hasBar()false を返す場合、bar を空のインスタンスに設定し、その後 bar の値を返します。これを呼び出した後、hasBar()true を返します。

繰り返しフィールド

このフィールド定義の場合、

repeated int32 foo = 1;

コンパイラは以下を生成します。

  • List<int> get foo: フィールドをサポートするリストを返します。フィールドが設定されていない場合、空のリストを返します。リストへの変更はフィールドに反映されます。

Int64 フィールド

このフィールド定義の場合、

// proto2
optional int64 bar = 1;

// proto3
int64 bar = 1;

コンパイラは以下を生成します。

  • Int64 get bar: フィールド値を含む Int64 オブジェクトを返します。

Int64 は Dart のコアライブラリに組み込まれていないことに注意してください。これらのオブジェクトを操作するには、Dart の fixnum ライブラリをインポートする必要がある場合があります。

import 'package:fixnum/fixnum.dart';

マップフィールド

次のようなmapフィールド定義が与えられた場合、

map<int32, int32> map_field = 1;

コンパイラは以下のゲッターを生成します。

  • Map<int, int> get mapField: フィールドをサポートするDartのマップを返します。フィールドが設定されていない場合、空のマップを返します。マップへの変更はフィールドに反映されます。

Any

次のようなAnyフィールドが与えられた場合、

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  google.protobuf.Any details = 2;
}

生成されたコードでは、details フィールドのゲッターは com.google.protobuf.Any のインスタンスを返します。これにより、Any の値をパックおよびアンパックするための以下の特殊なメソッドが提供されます。

    /// Unpacks the message in [value] into [instance].
    ///
    /// Throws a [InvalidProtocolBufferException] if [typeUrl] does not correspond
    /// to the type of [instance].
    ///
    /// A typical usage would be `any.unpackInto(new Message())`.
    ///
    /// Returns [instance].
    T unpackInto<T extends GeneratedMessage>(T instance,
        {ExtensionRegistry extensionRegistry = ExtensionRegistry.EMPTY});

    /// Returns `true` if the encoded message matches the type of [instance].
    ///
    /// Can be used with a default instance:
    /// `any.canUnpackInto(Message.getDefault())`
    bool canUnpackInto(GeneratedMessage instance);

    /// Creates a new [Any] encoding [message].
    ///
    /// The [typeUrl] will be [typeUrlPrefix]/`fullName` where `fullName` is
    /// the fully qualified name of the type of [message].
    static Any pack(GeneratedMessage message,
        {String typeUrlPrefix = 'type.googleapis.com'});

Oneof

次のようなoneof定義が与えられた場合、

message Foo {
  oneof test {
    string name = 1;
    SubMessage sub_message = 2;
  }
}

コンパイラは以下のDart列挙型を生成します。

 enum Foo_Test { name, subMessage, notSet }

さらに、これらのメソッドも生成します。

  • Foo_Test whichTest(): どのフィールドが設定されているかを示す列挙型を返します。いずれも設定されていない場合、Foo_Test.notSet を返します。
  • void clearTest(): 現在設定されているoneofフィールドの値(もしあれば)をクリアし、oneofケースをFoo_Test.notSetに設定します。

oneof 定義内の各フィールドに対して、通常のフィールドアクセサメソッドが生成されます。例えば、name の場合、

  • String get name: oneof のケースが Foo_Test.name の場合、フィールドの現在の値を返します。それ以外の場合、デフォルト値を返します。
  • set name(String value): フィールドの値を設定し、oneof のケースを Foo_Test.name に設定します。これを呼び出した後、get namevalue を返し、whichTest()Foo_Test.name を返します。
  • void clearName(): oneof のケースが Foo_Test.name でない場合、何も変更されません。それ以外の場合、フィールドの値をクリアします。これを呼び出した後、get name はデフォルト値を返し、whichTest()Foo_Test.notSet を返します。

列挙型

次のような列挙型定義が与えられた場合、

enum Color {
  COLOR_UNSPECIFIED = 0;
  COLOR_RED = 1;
  COLOR_GREEN = 2;
  COLOR_BLUE = 3;
}

プロトコルバッファコンパイラは、ProtobufEnum クラスを拡張する Color というクラスを生成します。このクラスには、4つの各値に対応する static const Color と、これらの値を含む static const List<Color> が含まれます。

static const List<Color> values = <Color> [
  COLOR_UNSPECIFIED,
  COLOR_RED,
  COLOR_GREEN,
  COLOR_BLUE,
];

また、以下のメソッドも含まれます。

  • static Color? valueOf(int value): 与えられた数値に対応する Color を返します。

各値は以下のプロパティを持ちます。

  • name: .proto ファイルで指定された列挙型の名前。
  • value: .proto ファイルで指定された列挙型の整数値。

.proto 言語では、複数の列挙型シンボルが同じ数値を持つことができることに注意してください。同じ数値を持つシンボルは同義語です。例えば、

enum Foo {
  BAR = 0;
  BAZ = 0;
}

この場合、BAZBAR の同義語であり、次のように定義されます。

static const Foo BAZ = BAR;

列挙型はメッセージ型の中にネストして定義できます。例えば、次のような列挙型定義が与えられた場合、

message Bar {
  enum Color {
    COLOR_UNSPECIFIED = 0;
    COLOR_RED = 1;
    COLOR_GREEN = 2;
    COLOR_BLUE = 3;
  }
}

プロトコルバッファコンパイラは、GeneratedMessage を拡張する Bar というクラスと、ProtobufEnum を拡張する Bar_Color というクラスを生成します。

拡張 (proto2 のみ)

拡張範囲を持つメッセージとトップレベルの拡張定義を含むファイル foo_test.proto が与えられた場合、

message Foo {
  extensions 100 to 199;
}

extend Foo {
  optional int32 bar = 101;
}

プロトコルバッファコンパイラは、Foo クラスに加えて、ファイル内の各拡張フィールドに対して static Extension を含む Foo_test クラスと、ExtensionRegistry にすべての拡張を登録するメソッドを生成します。

  • static final Extension bar
  • static void registerAllExtensions(ExtensionRegistry registry) : 指定されたレジストリに、定義されているすべての拡張を登録します。

Foo の拡張アクセサは次のように使用できます。

Foo foo = Foo();
foo.setExtension(Foo_test.bar, 1);
assert(foo.hasExtension(Foo_test.bar));
assert(foo.getExtension(Foo_test.bar)) == 1);

拡張は、別のメッセージの中にネストして宣言することもできます。

message Baz {
  extend Foo {
    optional int32 bar = 124;
  }
}

この場合、拡張 bar は代わりに Baz クラスの静的メンバーとして宣言されます。

拡張を持つ可能性のあるメッセージをパースする場合、パースしたいすべての拡張を登録した ExtensionRegistry を提供する必要があります。そうしないと、それらの拡張は未知のフィールドとして扱われます。例えば、

ExtensionRegistry registry = ExtensionRegistry();
registry.add(Baz.bar);
Foo foo = Foo.fromBuffer(input, registry);

未知のフィールドを持つ既にパースされたメッセージがある場合、ExtensionRegistryreparseMessage を使用してメッセージを再パースできます。未知のフィールドのセットにレジストリに存在する拡張が含まれている場合、これらの拡張はパースされ、未知のフィールドセットから削除されます。メッセージに既に存在する拡張は保持されます。

Foo foo = Foo.fromBuffer(input);
ExtensionRegistry registry = ExtensionRegistry();
registry.add(Baz.bar);
Foo reparsed = registry.reparseMessage(foo);

拡張を取得するこの方法は、全体的にコストが高いことに注意してください。可能な場合は、GeneratedMessage.fromBuffer を実行する際に、必要なすべての拡張を持つ ExtensionRegistry を使用することをお勧めします。

サービス

サービス定義が与えられた場合、

service Foo {
  rpc Bar(FooRequest) returns(FooResponse);
}

プロトコルバッファコンパイラは `grpc` オプション (例: --dart_out=grpc:output_folder) を付けて呼び出すことができ、その場合、gRPC をサポートするためのコードを生成します。詳細については、gRPC Dart クイックスタートガイドを参照してください。