Description
I've been trying to find a neat way to deal with serialising a number of different types behind an interface. So far I've hit on two things that appear to work. One is to introduce an intermediate structure that has a 2-tuple of type/data, the other is to hack in a msgpack extension (which you explicitly recommend against in #35 - might be good to add that recommendation to the docs!).
In spite of the recommendation, the latter does indeed work, though not particularly well. It introduces a lot of intermediate copying of bytes and calculating Len()
is difficult. I worked around that by marshalling in Len()
and caching the result for the subsequent marshal, but you know... ew.
At the moment, I only have a few structures in my application that need this, but we are looking to serialise a heap more stuff so that's set to explode and a lot of the new stuff requires some sort of polymorhpism. I've been surviving by doing my own code generation of manually written msgp calls for the wrapper structs, but I'd really like a way to do this that doesn't require the intermediate struct because that will gunk up the application pretty badly.
I've been thinking something like another directive, like //msgp:maptype ...
, which would inline [type, data] as a 2-length array in place of the value itself. I'm imagining it could look something like this:
//msgp:generate
//msgp:maptype Thing as:string using:thingToKind/kindToThing
func thingToKind(thing Thing) (kind string, err error) {
switch thing.(type) {
case ConcreteThing:
kind = "concrete"
default:
err = fmt.Errorf("unknown thing %T", thing)
}
return
}
func kindToThing(kind string) (thing Thing, err error) {
switch kind {
case "concrete":
thing = &ConcreteThing{}
default:
err = fmt.Errorf("unknown kind %s", kind)
}
return
}
type Thing interface {}
type ConcreteThing struct {
Empty int
}
type Pants struct {
Thing Thing
}
The mappers themselves are a bit annoying to write but it does seem to work well. I use ints for the kind but some may want to use strings.
At the moment, my janky hack version is producing Encoders that look like this:
// EncodeMsg implements msgp.Encodable
func (z *Msg) EncodeMsg(en *msgp.Writer) (err error) {
var kind int
var msg msgpsrv.Msg
if z != nil {
msg = z.Msg
kind, err = mapper.Kind(msg)
if err != nil {
return
}
}
// array header, size 2
err = en.Append(0x92)
if err != nil {
return err
}
err = en.WriteInt(kind)
if err != nil {
return
}
if msg != nil {
err = msg.EncodeMsg(en)
} else {
err = en.WriteNil()
}
return
}
If this (or something else that achieves the same goal) is something you'd consider, I'm quite happy to try to work up a PR that meets your requirements. It'd be great to see some support for this in msgp, it's a pretty common problem.