Dart で生成されたコード

任意のプロトコル定義に対してプロトコルバッファコンパイラが生成する Dart コードについて説明します。

proto2 と proto3 で生成されるコードの相違点を強調表示しています。これらの相違点は、このドキュメントで説明されている生成されたコードにあり、ベース API にはないことに注意してください。ベース API は両方のバージョンで同じです。このドキュメントを読む前に、proto2 言語ガイド および/または proto3 言語ガイド をお読みください。

コンパイラの起動

プロトコルバッファコンパイラは、Dart コードを生成するための プラグイン を必要とします。手順 に従ってインストールすると、--dart_out コマンドラインフラグで呼び出されたときに protoc が使用する protoc-gen-dart バイナリが提供されます。--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.proto および src/bar/baz.proto を読み取ります。build/gen/foo.pb.dart および build/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 {
  }
}

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

フィールド

前のセクションで説明したメソッドに加えて、プロトコルバッファコンパイラは、.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 タイプは スカラー値型テーブル に従って選択されます。メッセージ型および enum 型の場合、値型はメッセージまたは enum クラスに置き換えられます。

単数プリミティブフィールド (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 フィールド定義が次のように与えられた場合

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 型を生成します。

 enum Foo_Test { name, subMessage, notSet }

さらに、次のメソッドを生成します。

  • Foo_Test whichTest(): 設定されているフィールドを示す enum を返します。いずれも設定されていない場合は 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 定義が次のように与えられた場合

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 ファイルで指定された enum の名前。
  • value: .proto ファイルで指定された enum の整数値。

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

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

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

static const Foo BAZ = BAR;

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

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 と、ExtensionRegistry 内のすべての拡張機能を登録するためのメソッドを含む Foo_test クラスを生成します。

  • 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 クイックスタートガイド を参照してください。