C ++ Protobufs :: Как стереть определенное поле с помощью MergeFrom ()?

3

Прежде всего: я не специалист в 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 (по умолчанию), чем:

  • MergeFrom() не будет изменять A;
  • CopyFrom() также удалит другие поля.

Другой способ - заполнить B содержимым поля A, clear name, а component1 будет использовать CopyFrom(). Но это неприемлемо, потому что система действительно загружена, и может быть много других полей. Итак, желаемое решение для очистки поля имени:

  • Компонент2 создает сообщение B.
  • Явно сохраняет информацию о том, что она хочет удалить только поле имени.
  • Компонент1 выполняет A.MergeFrom(B).
  • Результат: A:: name очищается, но другие поля остаются нетронутыми.

Насколько я проверял, это относится к повторным и необязательным полям. Есть ли готовое к использованию решение, или я должен изменить реализацию protobuf?

  • 0
    Как насчет установки B.name в какое-то специальное значение («DELETE_ME»), выполнить обычное слияние, но затем просканировать все поля и удалить те, чье значение является специальным?
  • 0
    Да. Это возможное решение. Хорошо, что он не требует изменений в реализации protobuf. Но мне также стоит выбрать такое специальное значение для других типов, например int32. Здесь сложнее. Это будет большой переключатель, который определяет тип поля, а затем сравнивает его со специальным значением.
Показать ещё 1 комментарий
Теги:
protocol-buffers

4 ответа

0

Расширяющийся подход с полевыми масками (предложенный Кентоном Вардой):

Примечание. Для этого решения требуется 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());
}
0

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());
}
  • 0
    Не переопределяйте MergeFrom() . Вы сломаете части реализации protobuf, которые ожидают, что MergeFrom() будет работать определенным образом. Вместо этого поместите ваш основанный на отражении код в качестве отдельной функции - нет никакой причины, по которой он конкретно должен быть в MergeFrom() , поскольку вы можете иметь свой код, который вызывает этот вызов, вместо этого MergeFrom() новую функцию. Также, таким образом, вам вообще не нужно наследовать пользователя.
  • 0
    (В противном случае код выглядит разумным.)
Показать ещё 1 комментарий
0

Вы не можете решить эту проблему с помощью базового 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, в котором будут указаны именно те поля, которые вас интересуют, чтобы другие поля остались нетронутыми.

  • 0
    Привет Кентон, не могу понять, как его использовать. Можете ли вы дать мне маленький пример?
  • 0
    Хорошо. Мне удалось его использовать: в файле .proto я добавил: 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 ниже.
0

В вашем случае нет встроенного решения protobuf. Очевидным решением было бы перебрать все поля в сообщении A и проверить, присутствует ли это поле в сообщении B, если вы не можете его очистить.

  • 0
    Но в этом случае, если я отправлю сообщение очистки B, все поля A будут очищены, верно? Но я хочу очистить только одно поле.
  • 0
    Да, нет способа пометить поле как очищаемое. Если у вас есть полный контроль над типами сообщений, вы можете добавить повторяющееся строковое поле, которое содержит поля, которые вы хотите очистить.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню