Ephesiel
Ephesiel

Reputation: 141

Why my Protobuf class can parse a string serialized by an other Protobuf class

I tried to know what package I got in my tcp socket, so I use protobuf. But when I SerializeToString my first protobuf class, the ParseFromString method of an other protobuf class returns true.

The two classes are differents

Here are my .proto

syntax = "proto3";

package protobuf;
message Message
{
    string content = 1;
}
message Player
{
    int32 id = 1;
    string name = 2;
}

Here is my c++ code

auto messageProto = new protobuf::Message;
messageProto->set_content("Hello");

std::string data;
messageProto->SerializeToString(&data);

protobuf::Player player;
if (player.ParseFromString(data))
{
    qDebug() << "parse player";
}

protobuf::Message message2;
if (message2.ParseFromString(data))
{
    qDebug() << "parse message";
}

Output :

parse player
parse message

Why ?

Upvotes: 1

Views: 1504

Answers (2)

Ephesiel
Ephesiel

Reputation: 141

EDIT : So, now, is it the best approach ?

syntax = "proto3";

message Header
{
    oneof payload
    {
        Message message = 1;
        Player player = 2;
    }
}
message Message
{
    string content = 1;
}
message Player
{
    int32 id = 1;
    string name = 2;
}

When I write :

void SocketManager::sendData(Player& player)
{
    Header header;
    header.set_allocated_player(&player);
    write(header);
}

void SocketManager::sendData(Message& message)
{
    Header header;
    header.set_allocated_message(&message);
    write(header);
}

// etc... for each kind of message

When I read :

void read(const std::string& data)
{
    protobuf::Header header;
    header.ParseFromString(data);

    switch (header.payload_case())
    {
    case protobuf::Header::kMessage:
        emit messageProtoReceived(header.message());
        break;
    case protobuf::Header::kPlayer:
        emit playerProtoReceived(header.player());
        break;
    case protobuf::Header::PAYLOAD_NOT_SET:
        qDebug() << "Error, the payload isn't set, please create a header with a payload";
        break;
    }
}

Upvotes: 0

Marc Gravell
Marc Gravell

Reputation: 1063338

My recommended solution to the problem of multiple different payloads:

syntax = "proto3";

package protobuf;
message RenameMe // the outer payload wrapper
{
  oneof payload
  {
    Foo foo = 1;
    Bar bar = 2;
  }
}
message Foo // one type of content
{
    string content = 1;
}
message Bar // another type of content
{
    int32 id = 1;
    string name = 2;
}

Now you can just deserialize everything as a RenameMe (naming is hard!) and check the payload discriminated union enum to see how you should interpret the data. Then just access foo or bar respectively.

This approach is clear, obvious, and readily and effectively extensible into additional message types. The testing can be implemented with switch in many programming languages. This style also works well with polymorphism in some environments - for example, in C# with protobuf-net that could be serialized/deserialized with:

[ProtoContract, ProtoInclude(1, typeof(Foo)), ProtoInclude(2, typeof(Bar))]
abstract class RenameMe {}

[ProtoContract]
class Foo : RenameMe {
  [ProtoMember(1)] public string Content {get;set;}
}

[ProtoContract]
class Bar : RenameMe {
  [ProtoMember(1)] public int Id {get;set;}
  [ProtoMember(2)] public string Name {get;set;}
}

Upvotes: 1

Related Questions