Protobuf.js: Example for how to work with oneofs

Created on 7 Dec 2016  路  5Comments  路  Source: protobufjs/protobuf.js

I've been hunting around for some up to date documentation that explains the basics of how to work with oneof fields in protobuf.js v6, but haven't been able to find anything. If there are some examples, could you point me in the right direction, and if not, would you mind giving a brief example of creating, encoding, and decoding a message that has a oneof field? Thanks.

compatibility question

Most helpful comment

After messing around a bit longer, I got to something that seems to be working, but I'm not sure if this is the proper approach:

For a proto file "simple.proto":

syntax = "proto2";

message MsgType1 {
    required int32 value = 1;
}

message MsgType2 {
    required bool value = 1;
}

message MsgType3 {
    required int32 value1 = 1;
    required int32 value2 = 2;
}

message MyMessage {
    required uint32 uid = 1;
    required uint32 pid = 2;
    required uint32 utime = 3;

    oneof payload {
        MsgType1 msg1 = 4;
        MsgType2 msg2 = 5;
        MsgType3 msg3 = 6;
    }
}
const protobuf = require( "protobufjs" );

protobuf.load("simple.proto", function(err, root) 
{
    if (err) throw err;

    // Lookup message type
    var MyMessage = root.lookup("MyMessage");

    // Create
    var myMessage = MyMessage.create( 
    {
        uid:1,
        pid:2,
        utime:3,
        msg2: { value: true},
        msg1: { value: 7}
    });

    // Encode
    var bb = MyMessage.encode( myMessage ).finish();

    // Decode
    var decodedMessage = MyMessage.decode(bb);
    var which = decodedMessage.payload;
})

Anything more to this? More idiomatic approach?

All 5 comments

After messing around a bit longer, I got to something that seems to be working, but I'm not sure if this is the proper approach:

For a proto file "simple.proto":

syntax = "proto2";

message MsgType1 {
    required int32 value = 1;
}

message MsgType2 {
    required bool value = 1;
}

message MsgType3 {
    required int32 value1 = 1;
    required int32 value2 = 2;
}

message MyMessage {
    required uint32 uid = 1;
    required uint32 pid = 2;
    required uint32 utime = 3;

    oneof payload {
        MsgType1 msg1 = 4;
        MsgType2 msg2 = 5;
        MsgType3 msg3 = 6;
    }
}
const protobuf = require( "protobufjs" );

protobuf.load("simple.proto", function(err, root) 
{
    if (err) throw err;

    // Lookup message type
    var MyMessage = root.lookup("MyMessage");

    // Create
    var myMessage = MyMessage.create( 
    {
        uid:1,
        pid:2,
        utime:3,
        msg2: { value: true},
        msg1: { value: 7}
    });

    // Encode
    var bb = MyMessage.encode( myMessage ).finish();

    // Decode
    var decodedMessage = MyMessage.decode(bb);
    var which = decodedMessage.payload;
})

Anything more to this? More idiomatic approach?

Using MyMessage.create (just as you do) instead of plain objects is recommended when using oneofs, because runtime messages have virtual oneof properties with proper side effects.

    var myMessage = MyMessage.create( 
    {
        uid:1,
        pid:2,
        utime:3,
        msg2: { value: true},
        msg1: { value: 7}
    });

When creating a message with oneofs, the last declared field value that is part of the oneof will cause the virtual oneof field to be set to this field's name (here: payload = "msg1"). Other field values part of the same oneof will be deleted (here: msg2, only msg1 is encoded). (see: Prototype constructor, setVirtual)

Setting a oneof field will automatically clear all other members of the oneof. So if you set several oneof fields, only the last field you set will still have a value. 1

When decoding, the virtual oneof field should point to the field name of the last field of the oneof that was present on the wire. However, if multiple fields were present (which should be prevented usually), all of the present field values are set on the wire, but the virtual field will still just point to the last seen on the wire. (see getVirtual - this actually returns the first defined oneof field's name present on the wire, which is not to the spec and should be fixed).

If the parser encounters multiple members of the same oneof on the wire, only the last member seen is used in the parsed message. 1

When working with oneofs, like changing the field, you can set the virtual oneof field to the field name of the field you want to be the sole field to encode (see setVirtual above):

myMessage.msg2 = ... ;
myMessage.payload = "msg2"; // deletes msg1 - use .setPayload("msg2") to support IE8

Great, thanks for the additional information and for the great work you've put into this!

is there other better way for decodedMsg than the code below?
switch(decodedMessage.payload)
{
case "msg1":
break;
case "msg2":
break;
case "msg3":
break;
case "msg4":
break;
defalut:
break;
}
/////////////////////////////////////////////////////
as i know in c++, protobuf have a _case() function and a related enum, so it can be like this:
switch(pDecodedMsg->payload_case())
{
case package::MyMessage::kMsgType1:
break;
case package::MyMessage::kMsgType2:
break;
case package::MyMessage::kMsgType3:
break;
...
}

When working with oneofs, like changing the field, you can set the virtual oneof field to the field name of the field you want to be the sole field to encode (see setVirtual above):

myMessage.msg2 = ... ;
myMessage.payload = "msg2"; // deletes msg1 - use .setPayload("msg2") to support IE8

I feel like this should be in the documentation as it's a pretty common case.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

RP-3 picture RP-3  路  4Comments

b1naryMan picture b1naryMan  路  4Comments

kostyay picture kostyay  路  3Comments

mj-mehdizadeh picture mj-mehdizadeh  路  5Comments

chloe2018s picture chloe2018s  路  3Comments