简体   繁体   中英

c++ protobuf oneof deserializing generically

I am wondering if there is a generic way to deserialize a message that has a oneof sub-message. The oneof message will contain many other messages. Right now, I can get it working using a switch statement on the case number and call the appropriate getter. But, if there is a generic way so that in the future I don't have to keep adding new cases.

I would like to eliminate the cases and have something generic where I could get the message type ie Exit and call the correct getter. Is there a way to do it with reflection? I looked for examples online but couldn't find much.

proto file:

message AllClientMessages {
    oneof client_message {
        util.client.command Exit exit = 1
        util.client.command SendNew sendnew = 2
        util.client.command CancelAll cancelAll = 3
        // ... (more than 60 messages)
    }
}

Code ( buf contains the serialized message):

const std::unique_ptr<google::protobuf::Message> ProtoBuilder::deserialize(char* buf)
{
    AllClientMessages clientMsg;
    clientMsg.ParseFromString(buf);

    switch(clientMsg.client_message_case())
    {
    case AllClientMessages::kExitFieldNumber: //generate enums
        return make_unique<Exit>(clientMsg.exit());

    case AllClientMessages::kSendNewFieldNumber:
        return make_unique<SendNew>(clientMsg.sendnew());

    // I access the getter and return unique_ptr to base protobuf Message.
    // ... I do this for each case.
    }
}

Here's some code that should work for this.

Some time at initialization you can construct a map between one-of field number and index.

auto descriptor = clientMsg.GetDescriptor();
auto oneofDescriptor = descriptor->FindOneofByName("client_message");

// Map from oneof field number to field index
std::unordered_map<int, int> numberToIndexMap;

for (int idx = 0; idx < oneofDescriptor->field_count(); idx++)
{
    auto fieldDescriptor = oneofDescriptor->field(idx);
    numberToIndexMap[fieldDescriptor->number()] = idx;
}

Then later on you can use that map to access the correct field indicated by the type_case .

auto reflection = clientMsg.GetReflection();
auto descriptor = clientMsg.GetDescriptor();
auto oneofDescriptor = descriptor->FindOneofByName("client_message");
const auto & message = reflection->GetMessage(clientMsg, oneofDescriptor->field(numberToIndexMap.at(int(event.type_case()))));

// If we want a new copy to return as unique ptr
auto copy = std::make_unique<google::protobuf::Message>(message.New());
copy->CopyFrom(message);

return copy;

I haven't been able to find a way to get the one-of descriptor without hardcoding the name of the one-of, which feels kind of lame. It would be nice to be about to get it directly from AllClientMessages.client_message().

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM