Search code examples
c++protocol-bufferscereal

Can Protobuf's oneof deserialization functionality be recreated using Cereal?


I am able to write code to deserialize a proto message that contains a oneof section without having to know beforehand what the oneof section contains. I can not figure out how to write a similar set of struct definitions that I can deserialize the same way using Cereal.

I was using Protobufs to serialize/deserialize some data, but I ran into the same problem as piaoxu. So I switched to using Cereal.

I have been able to convert all of the original proto definitions to C++ struct definitions that can be serialized using Cereal, except the proto definitions that use the oneof functionality.

Here is an example set of a proto definitions that I'd like to convert to structs:

syntax = "proto3";
package Messages;

message A {}

message B {}

message Message {
    oneof contents {
        A a = 1;
        B b = 2;
    }
}

Here is the corresponding C++ code I had written to deserialize and parse a received Message. Using the generated protobuf code, I was able to deserialize a Message without first knowing if it contains an A or a B:

void ParseData(const string& data) {
{
    auto message = new Messages::Message();
    message->ParseFromString(data);

    switch (message->contents_case()) {
        case Messages::Message::kA:
            std::cout << "I got an A!" << std::endl;
            break;
        case Messages::Message::kB:
            std::cout << "I got a B!" << std::endl;
            break;
        default:
            break;
    }
}

And here is my attempt at making an equivalent set of struct definitions:

struct A {};

struct B {};

template <class Contents>
struct Message {
    enum Type {
        A,
        B,
    };

    Type type;

    Contents contents;

    template <class Archive>
    void serialize(Archive& archive) {
        archive(type, contents);
    }
};

And I'm using these structs to serialize and send a message like so:

bool SendA() {
    Message<A> message{};
    ostringstream stream;

    message.type = Message::Type::A;
    message.contents = new A{};

    {
        cereal::PortableBinaryOutputArchive archive(stream);
        archive(message);
    }

    return SendData(stream.str());
}

This approach works until I try to deserialize a received Message. I would like to be able to deserialize a Message without first knowing if it contains an A or a B, but AFAIK this is not possible.

Is this indeed not possible using my current approach? And if so, is there another approach that will allow me to deserialize a similar set of structs without first knowing what it contains?


Solution

  • With Igor's comment, I was able to alter the Message struct to be deserializable without first knowing what it contained:

    struct A {};
    
    struct B {};
    
    struct Message {
        enum Type {
            A,
            B,
        };
    
        Type type;
    
        unique_ptr<::A> a = nullptr;
        unique_ptr<::B> b = nullptr;
    
        template <class Archive>
        void serialize(Archive& archive) {
            archive(type, contents);
        }
    };
    

    The trick was to not make the Message struct a template.

    My first approach at writing the Message struct was to included the templeted member variable contents for storing each possible inner struct (an A or a B). This requires you to know what a serialized Message structure contains before you can deserialize it, because you'd have to first create the specialized Message variable to deserialize the data into.

    Instead, you can create a unique_ptr for each possible inner data struct. This allows you to create a Message struct that isn't templated, and you don't have to create a specialized Message variable to deserialize the data into.