С++ protobuf: как перебирать поля сообщения?
Я новичок в protobuf, и я застрял в простой задаче: мне нужно перебирать поля сообщения и проверять его тип. Если тип - это сообщение, я буду делать то же самое рекурсивно для этого сообщения.
Например, у меня есть такие сообщения:
package MyTool;
message Configuration {
required GloablSettings globalSettings = 1;
optional string option1 = 2;
optional int32 option2 = 3;
optional bool option3 = 4;
}
message GloablSettings {
required bool option1 = 1;
required bool option2 = 2;
required bool option3 = 3;
}
Теперь, чтобы явно получить доступ к значению поля в С++, я могу сделать это:
MyTool::Configuration config;
fstream input("config", ios::in | ios::binary);
config.ParseFromIstream(&input);
bool option1val = config.globalSettings().option1();
bool option2val = config.globalSettings().option2();
и т.д. Этот подход не подходит в случае, если у него большое количество полей.
Могу ли я сделать это с помощью итерации и получить имя и тип поля? Я знаю, что есть дескрипторы типа и несколько называются отражениями, но у меня не было успеха в моих попытках.
Может ли кто-нибудь дать мне пример кода, если это возможно?
Спасибо!
Ответы
Ответ 1
Посмотрите, как библиотека Protobuf реализует класс TextFormat::Printer
, который использует дескрипторы и отражение для перебора полей и преобразования их в текст:
https://github.com/google/protobuf/blob/master/src/google/protobuf/text_format.cc#L1473
Ответ 2
Это старый, но, возможно, кому-то это поможет. Вот метод, который печатает содержимое сообщения protobuf:
void Example::printMessageContents(std::shared_ptr<google::protobuf::Message> m)
{
const Descriptor *desc = m->GetDescriptor();
const Reflection *refl = m->GetReflection();
int fieldCount= desc->field_count();
fprintf(stderr, "The fullname of the message is %s \n", desc->full_name().c_str());
for(int i=0;i<fieldCount;i++)
{
const FieldDescriptor *field = desc->field(i);
fprintf(stderr, "The name of the %i th element is %s and the type is %s \n",i,field->name().c_str(),field->type_name());
}
}
Вы можете найти в значения полей > FieldDescriptor Enum возможные значения, которые вы получаете от field->type
. Например, для типа сообщения вам нужно будет проверить, равен ли тип FieldDescriptor::TYPE_MESSAGE
.
Эта функция печатает все "метаданные" сообщения protobuf. Однако вам нужно отдельно проверить каждое значение, что такое тип, а затем вызвать соответствующую функцию getter с помощью Reflection.
Таким образом, используя это условие, мы можем извлечь строки:
if(field->type() == FieldDescriptor::TYPE_STRING && !field->is_repeated())
{
std::string g= refl->GetString(*m, field);
fprintf(stderr, "The value is %s ",g.c_str());
}
Однако поля могут повторяться или не повторяться, а для обоих типов полей используются разные методы. Поэтому здесь используется проверка, чтобы убедиться, что мы используем правильный метод. Для повторяющихся полей мы имеем, например, этот метод для строк:
GetRepeatedString(const Message & message, const FieldDescriptor * field, int index
)
Таким образом, он учитывает индекс повторного поля.
В случае FieldDescriptor типа Message, предоставленная функция будет печатать только имя сообщения, мы также лучше печатаем его содержимое.
if(field->type()==FieldDescriptor::TYPE_MESSAGE)
{
if(!field->is_repeated())
{
const Message &mfield = refl->GetMessage(*m, field);
Message *mcopy = mfield.New();
mcopy->CopyFrom(mfield);
void *ptr = new std::shared_ptr<Message>(mcopy);
std::shared_ptr<Message> *m =
static_cast<std::shared_ptr<Message> *>(ptr);
printMessageContents(*m);
}
}
И, наконец, если поле повторяется, вам нужно будет вызвать метод FieldSize
для отражения и повторить все повторяющиеся поля.