protobufでMessageをmergeする

# はじめに

`protobuf`で複数のMessageをmergeする方法を紹介します。

merge処理自体は素朴にfieldを順番にコピーしても実現できますが、
いくつかmerge用のAPIも用意されています。

うまく活用できれば各Message型に依存しない形でmerge処理を記述することができたりします。
まとまった情報が見つからなかったので備忘のため記事にしました。

誤りやお気づきの点などありましたら、ぜひご指摘いただければ幸いです。

以下の環境で動作確認しています。
言語はC++で確認していますが、思想的には他の言語でも同じ結果になってくれると信じてます。

```shell
$ 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`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message#Message.MergeFrom.details)

`message_b.MergeFrom(message_a)`とすることで、
`message_a`の中で値が設定されているfieldの値を`message_b`にコピーします。

`message_a`と`message_b`の両方に値が設定されているfieldがある場合は、`message_a`の値で上書きされます。
値が設定されているかどうかは[default](https://developers.google.com/protocol-buffers/docs/proto3#default)と異なる値かどうかで判定されます。
例えば、bool fieldにfalseを設定したMessageをmergeしても、falseはdefault値なので**上書きされません**。

repeated field (配列)の場合は、連結されます。

Messageの中にembedded message (Message内Message)がある場合、
そのembedded messageが同様のルールでmergeされます。

![Message::MergeFromを使ったmerge](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/eb794d01-67af-d708-99a8-a180427cc868.jpeg)


# [`FieldMaskUtil::MergeMessageTo`]()

`Message::MergeFrom`はお手軽なぶん融通が効きません。
特にdefault値かどうかで上書きするfieldを決定するロジックは、直感に反するケースがあるため不具合につながりかねません。
そこでもう少し柔軟にmergeするために用意されているAPIが`FieldMaskUtil::MergeMessageTo`です。

`FieldMaskUtil::MergeMessageTo`は`Message::MergeFrom`と比較して下記の２つの機能が追加されています。

- `FieldMask`によるmerge対象fieldの指定
- `FieldMaskUtil::MergeOptions`によるmergeロジックの指定

## `FieldMask`によるmerge対象fieldの指定

`FieldMask`はその名の通り、処理対象のfieldを指定するためのmaskです。

下記のように、fieldを指すpathを`FieldMask`に設定し、
その`FieldMask`を処理に渡すことで処理範囲を限定することができます。

```cpp
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::MergeMessageToを使ったmerge](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/48be20a5-2a05-1bd2-a720-1fbce16a1603.jpeg)


## `FieldMaskUtil::MergeOptions`によるmergeロジックの指定

`FieldMaskUtil::MergeOptions`を使って２種類のオプションを設定できます。

- `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にまるっと置き換えられるようになります。

```c++
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);
```

![replace_message_fieldsによる変化](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/fae3f907-e0fc-1a31-3999-dfc6201e011b.jpeg)


### `MergeOptions::replace_repeated_fields`

repeated fieldの扱いを変更できます。

デフォルトではrepeated fieldは連結されます。
`MergeOptions::replace_repeated_fields`をtrueに設定することで、
連結ではなくmerge元のrepeated fieldにまるっと置き換えられるようになります。

```c++
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);
```

![replace_repeated_fieldsによる変化](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/bb7f4b9b-6331-960f-c032-737023416fa3.jpeg)


## repeated embedded message fieldの各要素をmergeする

[FieldMaskの説明](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.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`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message#Reflection)を使うしかないと思います。

```c++
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));
}
```

![repeated embedded message fieldの各要素をmergeする](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/a4708afd-1183-0f75-6c51-d51ed8e58421.jpeg)


## 実験コード

今回の記事で使った実験コードは、以下に置いています。

[takeoverjp/protobuf-sandbox](https://github.com/takeoverjp/protobuf-sandbox/blob/master/merge_qiita.cc)

# 参考

[Message](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message)
[FieldMaskUtil](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.field_mask_util)
[FieldMask](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.FieldMask)
[C++ Generated Code](https://developers.google.com/protocol-buffers/docs/reference/cpp-generated#repeatedmessage)