protobuf3でオプションのフィールドを定義する方法


111

protobuf(proto3構文)のオプションフィールドでメッセージを指定する必要があります。プロト2構文に関して、私が表現したいメッセージは次のようなものです。

message Foo {
    required int32 bar = 1;
    optional int32 baz = 2;
}

私の理解では、「オプションの」概念は構文プロト3から削除されました(必須の概念とともに)。代替案は明確ではありませんが、デフォルト値を使用して、送信者からフィールドが指定されていないことを示しますが、デフォルト値が有効な値のドメインに属している場合はあいまいさを残します(たとえばブール型を検討してください)。

では、上記のメッセージをどのようにエンコードするのですか?ありがとうございました。


以下のアプローチは適切な解決策ですか?メッセージNoBaz {}メッセージFoo {int32 bar = 1; oneof baz {NoBaz undefined = 2; int32定義= 3; }; }
MaxP 2017年

2
他の人がこれを見つけたが
Proto2

1
proto3は、基本的にすべてのフィールドをオプションにします。ただし、スカラーの場合、「フィールドが設定されていない」と「フィールドが設定されているがデフォルト値になっている」を区別できませんでした。スカラーをシングルトンoneofでラップする場合、たとえば--message blah {oneof v1 {int32 foo = 1; }}の場合、fooが実際に設定されているかどうかを再度確認できます。少なくともPythonの場合、fooがoneofの内部にないかのように直接操作でき、HasField( "foo")に問い合わせることができます。
jschultz4 1020

1
@MaxP新しいバージョンのprotobuf3が現在optional
SebastianK

回答:


54

protobufリリース3.12以降、proto3は、optionalキーワードを使用して(proto2と同様に)スカラー場のプレゼンス情報を提供するための実験的なサポートを提供しています。

syntax = "proto3";

message Foo {
    int32 bar = 1;
    optional int32 baz = 2;
}

A has_baz()/hasBaz()方法がために生成されoptional、それはproto2にあったと同じように、上のフィールド。

内部的には、Cyber​​Snoopyの回答が示唆optionalしているように、protocはフィールドをoneofラッパーを使用して宣言されているかのように効果的に処理します。

message Foo {
    int32 bar = 1;
    oneof optional_baz {
        int32 baz = 2;
    }
}

すでにそのアプローチを使用している場合は、ワイヤー形式が同じであるため、proto3サポートが実験ステータスを卒業したら、メッセージ宣言をクリーンアップ(からoneofに切り替えるoptional)できますoptional

フィールドプレゼンスとoptionalproto3の詳細については、アプリケーションノート:フィールドプレゼンスのドキュメントをご覧ください

--experimental_allow_proto3_optionalリリース3.12でこの機能を使用するには、フラグをprotocに渡します。機能の発表は、それが「うまくいけば3.13で一般的に利用可能」になりますと言います。

2020年11月の更新:この機能は、リリース3.14でもまだ実験的(フラグが必要)と見なされています。ある兆候行われて進歩のは。


2
C#でフラグを渡す方法を知っていますか?
JamesHancock20年

proto3がより良い構文を追加したので、これが最良の答えです。素晴らしいコールアウトジャラド!
EvanMoran20年

追加するだけですoptional int xyz:1)has_xyzオプションの値が設定されているかどうかを検出します2)clear_xyz値を設定解除します。詳細:github.com/protocolbuffers/protobuf/blob/master/docs/...
エヴァンモラン

@JamesHancockまたはJava?
TobiAkinyemi20年

1
@JónásBalázsリリース3.12でこの機能を使用するには、-experimental_allow_proto3_optionalフラグをprotocに渡します。
jaredjacobs

126

proto3では、すべてのフィールドは「オプション」です(送信者が設定に失敗してもエラーにはなりません)。ただし、フィールドがデフォルト値に明示的に設定されているか、まったく設定されていないかを区別する方法がないため、フィールドは「null許容」ではなくなりました。

「null」状態が必要な場合(そしてこれに使用できる範囲外の値がない場合)、代わりにこれを別のフィールドとしてエンコードする必要があります。たとえば、次のことができます。

message Foo {
  bool has_baz = 1;  // always set this to "true" when using baz
  int32 baz = 2;
}

または、次を使用することもできますoneof

message Foo {
  oneof baz {
    bool baz_null = 1;  // always set this to "true" when null
    int32 baz_value = 2;
  }
}

このoneofバージョンは、ネットワーク上でより明確で効率的ですが、oneof値がどのように機能するかを理解する必要があります。

最後に、もう1つの完全に合理的なオプションは、proto2を使用することです。Proto2は非推奨ではなく、実際、多くのプロジェクト(Google内を含む)はproto3で削除されたproto2機能に大きく依存しているため、切り替えられることはないでしょう。したがって、当面の間、それを使用し続けることは安全です。


あなたの解決策と同様に、私のコメントでは、実際の値とnullタイプ(空のメッセージ)を持つoneofを使用することを提案しました。このようにして、ブール値を気にしないでください(ブール値がある場合、baz_valueがないため、関係ありません)正しいですか?
MaxP 2017年

2
@MaxPソリューションは機能しますが、空のメッセージよりもブール値をお勧めします。どちらもネットワーク上で2バイトかかりますが、空のメッセージを処理するには、CPU、RAM、および生成されたコードの膨張がかなり多くなります。
ケントンバルダ2017年

13
メッセージFoo {oneof baz {int32 baz_value = 1; }}はかなりうまく機能します。
Cyber​​Snoopy 2017

@Cyber​​Snoopy回答として投稿できますか?あなたのソリューションは完璧でエレガントに機能します。
チェンチェン

@Cyber​​Snoopy次のような構造の応答メッセージを送信するときに、偶然に問題が発生したことがありますか。messageFooList {repated Foo foos = 1; }?あなたの解決策は素晴らしいですが、サーバー応答としてFooListを送信するのに問題があります。
caffeinate多くの場合、2018年

101

1つの方法はoptional、受け入れられた回答に記載されているようにすることです:https//stackoverflow.com/a/62566052/1803821

もう1つは、ラッパーオブジェクトを使用することです。グーグルがすでにそれらを提供しているので、あなたはそれらを自分で書く必要はありません:

.protoファイルの先頭に次のインポートを追加します。

import "google/protobuf/wrappers.proto";

これで、すべての単純なタイプに特別なラッパーを使用できます。

DoubleValue
FloatValue
Int64Value
UInt64Value
Int32Value
UInt32Value
BoolValue
StringValue
BytesValue

したがって、元の質問に答えるには、このようなラッパーの使用法は次のようになります。

message Foo {
    int32 bar = 1;
    google.protobuf.Int32Value baz = 2;
}

たとえばJavaでは、次のようなことができます。

if(foo.hasBaz()) { ... }


3
これはどのように作動しますか?ときはbaz=null、いつbaz渡されていない、両方のケースはhasBaz()言いますfalse
mayankcpdixit 2018

1
考え方は単純です。ラッパーオブジェクト、つまりユーザー定義の型を使用します。これらのラッパーオブジェクトは欠落している可能性があります。私が提供したJavaの例は、gRPCを使用するときにうまく機能しました。
VM4

うん!私は一般的な考え方を理解していますが、実際に動作するのを見たかったのです。私が理解していないのは、(ラッパーオブジェクトでも)「欠落しているラッパー値とnullラッパー値を識別する方法は?
mayankcpdixit19年

2
これが進むべき道です。C#を使用すると、生成されたコードはNullable <T>プロパティを生成します。
アーロンヒュードン

6
オリジナルのawsnerよりも優れています!
DevAggarwal19年

33

ケントンの回答に基づくと、よりシンプルでありながら機能するソリューションは次のようになります。

message Foo {
    oneof optional_baz { // "optional_" prefix here just serves as an indicator, not keyword in proto2
        int32 baz = 1;
    }
}

これはオプション文字をどのように具体化しますか?
JFFIGK 2018

20
基本的に、oneofの名前は適切ではありません。それは「せいぜい1つ」を意味します。可能なnull値は常にあります。
ecl3ctic 2018

未設定のままにすると、値の大文字と小文字はNone(C#の場合)になります-選択した言語の列挙型を参照してください。
nitzel

はい、これはおそらくproto3でこの猫の皮を剥ぐための最良の方法です-たとえそれが.protoを少し醜くしたとしても。
jschultz4 1020

ただし、フィールドがないことを明示的にnull値に設定していると解釈できることを意味します。言い換えると、「オプションのフィールドが指定されていない」と「フィールドが意図的に指定されていない」との間にあいまいさがあり、「null」を意味します。そのレベルの精度が必要な場合は、google.protobuf.NullValueフィールドを追加して、「フィールドが指定されていない」、「フィールドが値Xとして指定されている」、「フィールドがnullとして指定されている」を区別できるようにすることができます。 。それはちょっと曖昧ですが、それはproto3がJSONのようにnullを直接サポートしていないためです。
jschultz4 1020

7

@cybersnoopyの提案をここで拡張するには

次のようなメッセージを含む.protoファイルがある場合:

message Request {
    oneof option {
        int64 option_value = 1;
    }
}

提供されているケースオプションを利用できます(Javaで生成されたコード)

したがって、次のようにコードを記述できます。

Request.OptionCase optionCase = request.getOptionCase();
OptionCase optionNotSet = OPTION_NOT_SET;

if (optionNotSet.equals(optionCase)){
    // value not set
} else {
    // value set
}

Pythonではさらに簡単です。request.HasField( "option_value")を実行できます。また、メッセージ内にこのようなシングルトンoneofがたくさんある場合は、通常のスカラーと同じように、含まれているスカラーに直接アクセスできます。
jschultz4 1020


1

意図するメッセージをエンコードする別の方法は、「セット」フィールドを追跡するために別のフィールドを追加することです。

syntax="proto3";

package qtprotobuf.examples;

message SparseMessage {
    repeated uint32 fieldsUsed = 1;
    bool   attendedParty = 2;
    uint32 numberOfKids  = 3;
    string nickName      = 4;
}

message ExplicitMessage {
    enum PARTY_STATUS {ATTENDED=0; DIDNT_ATTEND=1; DIDNT_ASK=2;};
    PARTY_STATUS attendedParty = 1;
    bool   indicatedKids = 2;
    uint32 numberOfKids  = 3;
    enum NO_NICK_STATUS {HAS_NO_NICKNAME=0; WOULD_NOT_ADMIT_TO_HAVING_HAD_NICKNAME=1;};
    NO_NICK_STATUS noNickStatus = 4;
    string nickName      = 5;
}

これは、フィールドの数が多く、割り当てられているフィールドの数が少ない場合に特に適しています。

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

import field_enum_example_pb2
m = field_enum_example_pb2.SparseMessage()
m.attendedParty = True
m.fieldsUsed.append(field_enum_example_pb2.SparseMessages.ATTENDEDPARTY_FIELD_NUMBER)

-1

もう1つの方法は、オプションのフィールドごとにビットマスクを使用できることです。値が設定されている場合はそれらのビットを設定し、値が設定されていないビットをリセットします

enum bitsV {
    baz_present = 1; // 0x01
    baz1_present = 2; // 0x02

}
message Foo {
    uint32 bitMask;
    required int32 bar = 1;
    optional int32 baz = 2;
    optional int32 baz1 = 3;
}

解析時にbitMaskの値を確認します。

if (bitMask & baz_present)
    baz is present

if (bitMask & baz1_present)
    baz1 is present

-2

参照をデフォルトのインスタンスと比較することで、初期化されているかどうかを確認できます。

GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}

1
多くの場合、デフォルト値はフィールドに対して完全に許容できる値であり、その状況では「フィールドがない」と「フィールドは存在するがデフォルトに設定されている」を区別できないため、これは適切な一般的なアプローチではありません。
jschultz4 1020
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.