Прежде всего: я не специалист в protobuf.
Предположим, что у меня такая структура сообщений:
package msg_RepAndOpt;
message RepAndOpt
{
repeated string name = 1;
optional string surname = 2;
...
// there are lots of others.
}
И у меня есть два компонента, у которых есть копии этого сообщения:
// component1:
RepAndOpt A;
A.add_name("Name");
A.set_surname("Surname");
// component2:
RepAndOpt B;
В моем случае компоненты изменяют эти сообщения через механизм транзакций. Это означает, что если один компонент меняет какое-либо поле, он также отправляет его другому компоненту для распространения этих изменений. Компонент-приемник выполняет слияние:
// Component2 modifies B and sends it to component1.
// Component1 perfoms merge:
A.MergeFrom(B);
Теперь, скажем, component2 хочет стереть поле "имя". Если он отправит четкое сообщение B (по умолчанию), чем:
Другой способ - заполнить B содержимым поля A, clear name, а component1 будет использовать CopyFrom(). Но это неприемлемо, потому что система действительно загружена, и может быть много других полей. Итак, желаемое решение для очистки поля имени:
Насколько я проверял, это относится к повторным и необязательным полям. Есть ли готовое к использованию решение, или я должен изменить реализацию protobuf?
Расширяющийся подход с полевыми масками (предложенный Кентоном Вардой):
Примечание. Для этого решения требуется proto3, однако исходное сообщение может быть объявлено синтаксисом proto2. (ссылка на доказательство)
Мы можем определить поле маски поля:
import "google/protobuf/field_mask.proto";
message RepAndOpt
{
repeated string name = 1;
optional string surname = 2;
optional google.protobuf.FieldMask field_mask = 3;
}
И вот использование теста:
RepAndOpt emulateSerialization(const RepAndOpt& B)
{
RepAndOpt BB;
std::string data;
B.SerializeToString(&data);
BB.ParseFromString(data);
return BB;
}
void mergeMessageTo(const RepAndOpt& src, RepAndOpt& dst)
{
dst.MergeFrom(src);
if (src.has_field_mask())
{
FieldMaskUtil::MergeOptions megreOpt;
megreOpt.set_replace_message_fields(true);
megreOpt.set_replace_repeated_fields(true);
FieldMaskUtil::MergeMessageTo(src, src.field_mask(), megreOpt, &dst);
}
}
TEST(RepAndOptTest, fix_merge_do_the_job_with_serialization_multiple_values)
{
RepAndOpt A;
A.add_name("A");
A.add_name("B");
A.add_name("C");
A.set_surname("surname");
RepAndOpt B;
B.add_name("A");
B.add_name("C");
B.mutable_field_mask()->add_paths("name");
mergeMessageTo(emulateSerialization(B), A);
EXPECT_EQ(2, A.name_size());
EXPECT_STREQ("A", A.name(0).c_str());
EXPECT_STREQ("C", A.name(1).c_str());
EXPECT_STREQ("surname", A.surname().c_str());
}
UPD: обновлено после комментариев Kenton Varda (см. ниже).
Развертывание одного из предыдущих ответов:
Есть способ решить эту проблему, добавив новое поле в определение сообщения (это работает для прото v2):
repeated int32 fields_to_copy = 15;
Это поле будет заполнено идентификатором полей, которые будут скопированы (не объединены) на стороне приемника.
Я также реализовал эту вспомогательную функцию:
// CopiableProtoMsg.hpp
#pragma once
#include <google/protobuf/message.h>
template <typename T>
void CopyMessageFields(const T& from, T& to)
{
const ::google::protobuf::Descriptor *desc = T::descriptor();
const ::google::protobuf::Reflection *thisRefl = from.GetReflection();
std::vector<const ::google::protobuf::FieldDescriptor*> fields;
int size = from.fields_to_copy_size();
for (int i = 0; i < size; ++i)
{
const ::google::protobuf::FieldDescriptor *field = desc->FindFieldByNumber(from.fields_to_copy(i));
fields.push_back(field);
}
T msgCopy(from);
thisRefl->SwapFields(&to, &msgCopy, fields);
to.clear_fields_to_copy();
}
Эта функция проверяет поле fields_to_copy и выполняет копирование (через SwapFields()).
Вот простой тест:
RepAndOpt emulateSerialization(const RepAndOpt& B)
{
RepAndOpt BB;
std::string data;
B.SerializeToString(&data);
BB.ParseFromString(data);
return BB;
}
TEST(RepAndOptTest, additional_field_do_the_job_with_serialization)
{
RepAndOpt A;
RepAndOpt B;
A.add_name("1");
A.add_name("2");
A.add_name("3");
A.set_surname("A");
B.add_name("1");
B.add_name("3");
B.add_fields_to_copy(RepAndOpt::kNameFieldNumber);
RepAndOpt recvB = emulateSerialization(B);
A.MergeFrom(recvB);
CopyMessageFields(recvB, A);
EXPECT_EQ(2, A.name_size());
EXPECT_STREQ("1", A.name(0).c_str());
EXPECT_STREQ("3", A.name(1).c_str());
EXPECT_TRUE(A.has_surname());
EXPECT_EQ(0, A.fields_to_copy_size());
}
MergeFrom()
. Вы сломаете части реализации protobuf, которые ожидают, что MergeFrom()
будет работать определенным образом. Вместо этого поместите ваш основанный на отражении код в качестве отдельной функции - нет никакой причины, по которой он конкретно должен быть в MergeFrom()
, поскольку вы можете иметь свой код, который вызывает этот вызов, вместо этого MergeFrom()
новую функцию. Также, таким образом, вам вообще не нужно наследовать пользователя.
Вы не можете решить эту проблему с помощью базового MergeFrom()
, но вы можете проверить их в библиотеке protobuf:
https://github.com/google/protobuf/blob/master/src/google/protobuf/field_mask.proto https://github.com/google/protobuf/blob/master/src/google/protobuf/util/field_mask_util.h
В частности, FieldMaskUtil::MergeMessageTo()
, похоже, делает то, что вы хотите. Вам нужно будет построить FieldMask
, в котором будут указаны именно те поля, которые вас интересуют, чтобы другие поля остались нетронутыми.
google.protobuf.FieldMask field_mask = 3;
на стороне отправителя ( .cpp): 'B.mutable_field_mask () -> add_paths ("name");' на стороне получателя (* .cpp): 'if (B.has_field_mask ()) {FieldMaskUtil :: MergeOptions megreOpt; megreOpt.set_replace_repeated_fields (истина); FieldMaskUtil :: MergeMessageTo (B, B.field_mask (), megreOpt, & A); } 'Это требует proto v3. @Kenton, пожалуйста, проверьте мою версию с Proto v2 ниже.
В вашем случае нет встроенного решения protobuf. Очевидным решением было бы перебрать все поля в сообщении A и проверить, присутствует ли это поле в сообщении B, если вы не можете его очистить.
B.name
в какое-то специальное значение («DELETE_ME»), выполнить обычное слияние, но затем просканировать все поля и удалить те, чье значение является специальным?