Skip to content

Instantly share code, notes, and snippets.

@arl
Last active April 28, 2022 10:00
Show Gist options
  • Save arl/3951041e403d0050d5b316f9da24d47d to your computer and use it in GitHub Desktop.
Save arl/3951041e403d0050d5b316f9da24d47d to your computer and use it in GitHub Desktop.
Gob encoding decoding with multiple types
module github.com/arl/gobtest
go 1.18
package main
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"log"
"time"
)
type Measure struct {
Timestamp time.Time
Name string
Type string
DisplayName string
Unit string
Data string
}
func NewMeasure() *Measure {
return &Measure{
Timestamp: time.Now(),
Name: "some name",
Type: "some type",
DisplayName: "some display name",
Unit: "some unit",
Data: "some data",
}
}
type Alert struct {
Timestamp time.Time
Name string
State string
Message string
}
func NewAlert() *Alert {
return &Alert{
Timestamp: time.Now(),
Name: "some name",
State: "some state",
Message: "some message",
}
}
func concreteHandler(r io.Reader) {
dec := gob.NewDecoder(r)
measure, err := gobDecodeConcrete[Measure](dec)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", measure)
alert, err := gobDecodeConcrete[Alert](dec)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", alert)
}
func dynamicHandler(r io.Reader) {
dec := gob.NewDecoder(r)
msg1, err := gobDecode(dec)
if err != nil {
log.Fatalf("msg1: %v", err)
}
msg2, err := gobDecode(dec)
if err != nil {
log.Fatalf("msg2: %v", err)
}
doSomethingDynamic := func(imsg any) {
switch msg := imsg.(type) {
case Measure:
fmt.Printf("%+v\n", msg)
case Alert:
fmt.Printf("%+v\n", msg)
default:
panic("")
}
}
doSomethingDynamic(msg1)
doSomethingDynamic(msg2)
}
func reader() io.Reader {
wire := bytes.Buffer{}
enc := gob.NewEncoder(&wire)
if err := gobEncode(enc, NewMeasure()); err != nil {
log.Fatal(err)
}
if err := gobEncode(enc, NewAlert()); err != nil {
log.Fatal(err)
}
return &wire
}
func main() {
// Register concrete types we want 'gob' to handle.
gob.RegisterName("measurement", Measure{})
gob.RegisterName("alert", Alert{})
// Handler acting on a single type (and 'sure' to receive that one)
concreteHandler(reader())
// Handler having dynamic behaviour based on the type of the message it receives.
dynamicHandler(reader())
}
/* 3 helper functions */
// gobEncode encodes msg into the gob encoder. Underlying type of 'msg' must
// have previously been registered via gob.Register or gob.RegisterName.
//
// NOTE:
//
// This thin wrapper is only required if we want to support handlers to which
// values we can sent values of different types (i.e types are not known at
// compile-time). If we don't need to support that specific use case, then just
// using plain gob.Encoder/Decoder is enough.
//
// However in case we want to support dynamicity, we need to encode messages as
// interface{} rather than directly with their concrete type. Doing so means we
// can decode messages into interface{}, and use type assertions to perform
// dynamic behaviour based on the type.
func gobEncode(enc *gob.Encoder, msg any) error {
err := enc.Encode(&msg)
if err != nil {
return fmt.Errorf("gobEncode: error encoding value of type %T: %v", msg, err)
}
return nil
}
// gobDecodeConcrete is also only useful when we want to support single handlers
// receiving mulitple types. thanks to generic, the returned T has the final,
// concrete type.
func gobDecodeConcrete[T any](dec *gob.Decoder) (*T, error) {
var t any
err := dec.Decode(&t)
if err != nil {
return nil, fmt.Errorf("gobDecodeConcrete: error decoding payload into %T: %v", t, err)
}
conc, ok := t.(T)
if !ok {
var actual T
return nil, fmt.Errorf("gobDecodeConcrete: type assertion failed, got %T want %T", t, actual)
}
return &conc, nil
}
// This does nothing more than what gob.Decoder does, but it's provided in order
// to make the API symmetric. It's less confusing to do gobEncode and gobDecode
// than gobEncode and gob.Encoder.Decode imho.
func gobDecode(dec *gob.Decoder) (any, error) {
var msg any
if err := dec.Decode(&msg); err != nil {
return nil, fmt.Errorf("gobDecode: error decoding: %v", err)
}
return msg, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment