Eugene
Eugene

Reputation: 4879

Dynamically create protocol buffer object

I'm working with a set of protocol buffers and I'm having trouble wrapping my head around what I need to do to instantiate them and it has to be calculated at runtime. I know I somehow need to use reflect to do this, but nothing seems to work. At compile time, I only know the first protocol buffer that I need to create. That buffer contains a field that tells me an integer id of the next one I need to create.

All of my buffers are defined in a package called kpb.

I find that first one like this:

info := &kpb.ArchiveInfo{}

err := proto.Unmarshal(<byte array>, info)

// error handling

messageType = *info.MessageType // 1

I have a map that defines the next buffers I need to call. That map is defined like this:

Note that all of the values of this map are proto.ProtoMessage objects, but using that seemed to cause more problems than it solved.

var registryMap = map[uint32]interface{}{
  1: &kpb.KNDocumentArchive{},
  ...etc
}

So when I reference this map, I'm doing it like this:

var klass interface{}

//stuff

klass = registryMap[messageType]
fmt.Println(klass) // *kpb.KNDocumentArchive

But, what I can't figure out is how to instantiate a variable with the proper type to Unmarshal the payload I have.

I can get the type of the klass by doing this:

klassType := reflect.TypeOf(klass)
fmt.Println(klassType) // kpb.KNDocumentArchive - as expected

But, if I try to create a new variable with it, I get an error

payloadObj := new(klass)
// klassType (variable of type reflect.Type) is not a type

So even though the type is kpb.KNDocumentArchive like I expect, it's still somehow reflect.Type

When I used proto.ProtoMessage as the type for the map return, I could get past this part and have the variable instantiated, but I couldn't pass that to proto.Unmarshal because it, rightly, expects the type to be kpb.KNDocumentArchive

What am I doing wrong here?

Upvotes: 1

Views: 1197

Answers (1)

Eugene
Eugene

Reputation: 4879

I was able to figure this out. I needed to use proto built in features. The end result was updating my map to this:

var registryMap = map[uint32]string{
  1: "KN.KNDocumentArchive",
}

And then the unmarshaling part was solved with this:

func GetProto(id uint32, messageBytes []byte) proto.Message {
    klass := registryMap[id]

    if klass == "" {
        panic("Don't know how to parse Protobuf message type " + fmt.Sprint(id))
    }

    messageName := protoreflect.FullName(klass)

    pbtype, err := protoregistry.GlobalTypes.FindMessageByName(messageName)

    if err != nil {
        panic(err)
    }

    msg := pbtype.New().Interface()
    err = proto.Unmarshal(messageBytes, msg)

    if err != nil {
        panic(err)
    }

    return msg
}

Upvotes: 1

Related Questions