John Hoffman
John Hoffman

Reputation: 15

Is there a way to extract a protobuf oneof int value?

TL;DR: In Go, is there any way to get the integer value of a protobuf oneof case?

Detail:

In C# I can easily query the integer value of a Oneof case using something like the following:

opcode = (int)ChannelMessage.ChannelActionOneofCase.Register; // opcode will equal 1

However in Golang, there does not appear to be anything that I can use to easily extract that integer value.

I know that I can switch on the type itself:

switch m.ChannelAction.(type) {
    
    case *proto.ChannelMessage_Register:
    ...

however in my case this will require me to unmarshal every message, which for certain types isn't strictly necessary since I'm required to send the opcode along every time.

If it's helpful, my ChannelMessage type looks like the following:

message ChannelMessage
{
    oneof ChannelAction
    {
        ChannelRegister register = 1;
        ChannelUnregister unregister = 2;
        ...
    }
}
    

Upvotes: 1

Views: 8258

Answers (2)

fizzie
fizzie

Reputation: 711

It's probably not what you want to actually do, but the google.golang.org/protobuf/reflect/protoreflect package does have the necessary functions, if you need to refer to the field numbers of the fields that are part of your oneof.

For example, assuming you've imported your protos as pb, to get the number 1 by name (as in your C# example) you can do:

desc := (&pb.ChannelMessage{}).ProtoReflect().Descriptor()
opcode := desc.Fields().ByName("register").Number()

(This isn't strictly specific to the oneof, since oneof fields are really just regular message fields with an additional constraint that only one of them may be set.)

Or to figure out which field number a oneof field is set to in message m without writing out a type switch, assuming you know one of them has definitely been set, you can do:

ref := m.ProtoReflect()
desc := ref.Descriptor()
num := ref.WhichOneof(desc.Oneofs().ByName("ChannelAction")).Number()

In both cases the result (opcode, num) will be a numeric type (protoreflect.FieldNumber = protowire.Number) that has an underlying type of int32 you can convert it to.

Upvotes: 2

Oleg Butuzov
Oleg Butuzov

Reputation: 5405

You are right, you can do that with type switch:

// my example used simple strings instead of custom messages.
example := proto.ChannelMessage{
    ChannelAction: &pbExample.ChannelMessage_Register{"foobar"},
}

t := example.GetChannelAction()
switch v := t.(type) {
case *pbExample.ChannelMessage_Register:
    fmt.Printf("register\n")
case *pbExample.ChannelMessage_Unregister:
    fmt.Printf("unregister\n")
default:
    fmt.Printf("I don't know about type %T!\n", v)
}

// what you also can do is to ask directly your oneOf case and try to typecast it.
val, ok := example.GetRegister().(int) // GetUnregister is other option.
if ok {
    // black magic happens here
}

Upvotes: 2

Related Questions