protobufでMessageをmergeする
はじめに
protobuf
で複数のMessageをmergeする方法を紹介します。
merge処理自体は素朴にfieldを順番にコピーしても実現できますが、
いくつかmerge用のAPIも用意されています。
うまく活用できれば各Message型に依存しない形でmerge処理を記述することができたりします。
まとまった情報が見つからなかったので備忘のため記事にしました。
誤りやお気づきの点などありましたら、ぜひご指摘いただければ幸いです。
以下の環境で動作確認しています。
言語はC++で確認していますが、思想的には他の言語でも同じ結果になってくれると信じてます。
$ lsb_release -d
Description: Ubuntu 18.04.5 LTS
$ uname -r
5.5.8
$ uname -m
x86_64
$ protoc --version
libprotoc 3.0.0
Message::MergeFrom
message_b.MergeFrom(message_a)
とすることで、
message_a
の中で値が設定されているfieldの値をmessage_b
にコピーします。
message_a
とmessage_b
の両方に値が設定されているfieldがある場合は、message_a
の値で上書きされます。
値が設定されているかどうかはdefaultと異なる値かどうかで判定されます。
例えば、bool fieldにfalseを設定したMessageをmergeしても、falseはdefault値なので上書きされません。
repeated field (配列)の場合は、連結されます。
Messageの中にembedded message (Message内Message)がある場合、
そのembedded messageが同様のルールでmergeされます。
FieldMaskUtil::MergeMessageTo
Message::MergeFrom
はお手軽なぶん融通が効きません。
特にdefault値かどうかで上書きするfieldを決定するロジックは、直感に反するケースがあるため不具合につながりかねません。
そこでもう少し柔軟にmergeするために用意されているAPIがFieldMaskUtil::MergeMessageTo
です。
FieldMaskUtil::MergeMessageTo
はMessage::MergeFrom
と比較して下記の2つの機能が追加されています。
-
FieldMask
によるmerge対象fieldの指定 -
FieldMaskUtil::MergeOptions
によるmergeロジックの指定
FieldMask
によるmerge対象fieldの指定
FieldMask
はその名の通り、処理対象のfieldを指定するためのmaskです。
下記のように、fieldを指すpathをFieldMask
に設定し、
そのFieldMask
を処理に渡すことで処理範囲を限定することができます。
FieldMask mask;
mask.add_paths("string_field");
mask.add_paths("bool_field");
mask.add_paths("repeated_int_field");
mask.add_paths("embedded_message_field.int_field");
FieldMaskUtil::MergeOptions merge_options;
FieldMaskUtil::MergeMessageTo(message_a, mask, merge_options, &message_b);
FieldMask
を指定することで不要な上書きを避けられるだけでなく、
仮にdefault値であったとしても上書きすることができます。
今回の例では"bool_field"
をFieldMask
に指定しているため、
bool型のdefault値であるfalse
であっても上書きしています。
FieldMaskUtil::MergeOptions
によるmergeロジックの指定
FieldMaskUtil::MergeOptions
を使って2種類のオプションを設定できます。
MergeOptions::replace_message_fields
MergeOptions::replace_repeated_fields
MergeOptions::replace_message_fields
embedded message fieldの扱いを変更できます。
デフォルトではembedded message fieldはMessage::MergeFrom
と同じ挙動をします。
MergeOptions::replace_message_fields
をtrueに設定することで、
値によらずmerge元のembedded message fieldにまるっと置き換えられるようになります。
FieldMask mask;
mask.add_paths("embedded_message_field");
FieldMaskUtil::MergeOptions merge_options;
merge_options.set_replace_message_field(true);
FieldMaskUtil::MergeMessageTo(message_a, mask, merge_options, &message_b);
MergeOptions::replace_repeated_fields
repeated fieldの扱いを変更できます。
デフォルトではrepeated fieldは連結されます。
MergeOptions::replace_repeated_fields
をtrueに設定することで、
連結ではなくmerge元のrepeated fieldにまるっと置き換えられるようになります。
FieldMask mask;
mask.add_paths("repeated_int_field");
FieldMaskUtil::MergeOptions merge_options;
merge_options.set_replace_repeated_field(true);
FieldMaskUtil::MergeMessageTo(message_a, mask, merge_options, &message_b);
repeated embedded message fieldの各要素をmergeする
FieldMaskの説明には、repeated fieldについて以下の記載があります。
A repeated field is not allowed except at the last position of a field mask.
つまり、残念ながらrepeated fieldの各要素のmergeを指定する方法はないということになります。
もちろん各要素自体はMessage
型なので、下記のように各要素ごとにmergeすることは可能ですが、
思いっきり具体Message
型に依存した実装になります。
この依存をなくすためにはReflection
を使うしかないと思います。
assert(message_a.repeated_embedded_message_field_size() ==
message_b.repeated_embedded_message_field_size());
int len = message_a.repeated_embedded_message_field_size();
for (int i = 0; i < len; i++) {
FieldMaskUtil::MergeMessageTo(message_a.repeated_embedded_message_field(i), mask,
merge_options,
message_b.mutable_repeated_embedded_message_field(i));
}
実験コード
今回の記事で使った実験コードは、以下に置いています。
参考
Author And Source
この問題について(protobufでMessageをmergeする), 我々は、より多くの情報をここで見つけました https://qiita.com/takeoverjp/items/ed7a3a65d01ed52f50c2著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .