Protobuf and Go: Handling Oneof Field type

msingh
3 min readMay 8, 2021

This tutorial provides a basic Go introduction to working with protocol buffer Oneof field type. This can come in handy, when dealing with messages carrying values, which can be one of many given types. Another use case is message values representing a collection of heterogenous types.

Pre-requisite : This post assumes reader has basic understanding of Go and Protobuf.

Defining the Message

  • Lets define a message that contains a collection of patching instructions
  • Each patching instruction can either be a Copy operation or an Insert operation. These are heterogenous types

This is a good use of Oneof type. Here is how we could define these —

A look at the generated Go file (using the protobuf compiler) shows the following

There ate two type defined representing the two instructions -

  • Patch_Copy : The type name is prefixed withPatch_ since Copy is defined as a nested type (inner message) within Patch type (outer message)
  • Patch_Insert representing an Insert type
  • Patch : This type contains one of Copy or Insert operation. It is represented by the isPatch_Op interface.
  • isPatch_Op interface is implemented by two structs, Patch_CopyOp and Patch_InsertOp . They provide the necessary indirection to the actual types, Patch_Copy and Patch_Insert while “implementing” the interface isPatch_Op.

Here is a visual representation of the different types and relationships -

Okay so we got the types generated in Go and understand how they are related. Let’s look at how we can Marshal and UnMarshal such values.

Marshalling

Marshalling requires building the structures in the same way as the hierarchy shows above .

Looking at the relationship picture, the code follows the same model to build Instruction set —

  • Create the operation struct(s) you need, Patch_Copy and Patch_Insert
  • They need to be wrapped by the corresponding Structs “implementing” the isPatch_Op interface — Patch_CopyOp and Patch_InsertOp for Patch_Copy and Patch_Insert respectively. These wrappers can now be added as op field in the Patch message
  • Slice of thosePatch messages are then added to Instruction message
  • Finally Instructions are marshalled to a file.

Reading the Message (Unmarshal)

We can think of isPatch_Op interface value internally holding values of a variety of concrete types and considers the interface to be the union of those types. Type assertions can be used to discriminate among these types dynamically and then deal with each case differently.

The code above uses type switching. A type switch enables a multi-way branch based on the interface value’s dynamic type.

switch op := patch.Op.(type) {....

The op := patch.Op.(type) binds the extracted value of the dynamic type to the variable op in this case. The case matching can now proceed and the statement in the case would be executed depending the actual type of the implementation (pointer to Patch_CopyOp or pointer to Patch_InsertOp )

Full source code for this example can be found at https://github.com/monmohan/protobuf-examples/blob/main/ex1/main.go

Running the example prints the following as expected —

$ go run ex1/main.goCopy Operation start: 10, end : 100Insert Operation Rawbytes length: 10

--

--