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 anInsert
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 typePatch
: This type contains one of Copy or Insert operation. It is represented by theisPatch_Op
interface.
isPatch_Op
interface is implemented by two structs,Patch_CopyOp
andPatch_InsertOp
. They provide the necessary indirection to the actual types,Patch_Copy
andPatch_Insert
while “implementing” the interfaceisPatch_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
andPatch_Insert
- They need to be wrapped by the corresponding Structs “implementing” the
isPatch_Op
interface —Patch_CopyOp
andPatch_InsertOp
forPatch_Copy
andPatch_Insert
respectively. These wrappers can now be added asop
field in thePatch
message - Slice of those
Patch
messages are then added toInstruction
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