-
Notifications
You must be signed in to change notification settings - Fork 193
Using Extensions
One of the most useful features of MessagePack is its support for arbitrary type system extensions. msgp
uses MessagePack's extension features to add support for Go's time.Time
, complex64
, and complex128
types. Additionally, msgp
allows users to define their own extension types.
On the wire, a MessagePack ext
is simply a tuple of (int8
, []byte
), where the int8
maps to a particular application-defined type.
The first step to implementing your own MessagePack type is implementing the msgp.Extension
interface for that type:
type Extension interface {
ExtensionType() int8
Len() int
MarshalBinaryTo([]byte) error
UnmarshalBinary([]byte) error
}
ExtensionType()
should always return the same value for a given concrete type, and simply identifies the type of the object whose data follows. Keep in mind that msgp
uses types 3, 4, and 5 for complex64
, complex128
, and time.Time
, respectively, and that types < 0 are reserved by the MessagePack spec.
Len()
should return the encoded length of the object, in bytes, given its current state.
MarshalBinaryTo([]byte)
should put the binary-encoded value of the object into the supplied []byte
. It will always be the case that the length of the supplied []byte
is whatever Len()
returned.
UnmarshalBinary
should decode the value of the object from the supplied []byte
.
The next step to using an extension with msgp
is registering the extension with the package during initialization. Although this step is optional, it is highly recommended, as it allows the decoding of
interface{}
to return the appropriate concrete type when it recognizes the extension type, and it also allows the JSON translation to use the canonical JSON form of your object when translating. (Bonus points if your type already implements the json.Marshaler
interface.)
In order for the code generator to recognize that you want a type to be treated as a msgp.Extension
instead of its concrete type, you must annotate the field with "extension"
directly:
type Thing struct {
Num Numbers `msg:"num,extension"`
}
type Numbers [4]float64
func (n *Numbers) ExtensionType() int8 { /* ... */ }
func (n *Numbers) Len() int { /* ... */ }
func (n *Numbers) MarshalBinaryTo(b []byte) error { /* ... */ }
func (n *Numbers) UnmarshalBinary(b []byte) error { /* ... */ }
package main
import (
"github.com/tinylib/msgp/msgp"
)
func init() {
// Registering an extension is as simple as matching the
// appropriate type number with a function that initializes
// a freshly-allocated object of that type
msgp.RegisterExtension(99, func() msgp.Extension { return new(RGBA) } )
}
// RGBA will be the concrete type
// of our new extension
type RGBA [4]byte
// Here, we'll pick an arbitrary number between
// 0 and 127 that isn't already in use
func (r *RGBA) ExtensionType() int8 { return 99 }
// We'll always use 4 bytes to encode the data
func (r *RGBA) Len() int { return 4 }
// MarshalBinaryTo simply copies the value
// of the bytes into 'b'
func (r *RGBA) MarshalBinaryTo(b []byte) error {
copy(b, (*r)[:])
return nil
}
// UnmarshalBinary copies the value of 'b'
// into the RGBA object. (We might want to add
// a sanity check here later that len(b)==4.)
func (r *RGBA) UnmarshalBinary(b []byte) error {
copy((*r)[:], b)
return nil
}
Once we've written the boilerplate above, using our new type is as simple as:
type ColoredBox struct {
Height int `msg:"height`
Width int `msg:"width"`
Color RGBA `msg:"color,extension"`
}
Additionally, if we decide we want RGBA
represented a particular way
in JSON, all we need to do is implement the json.Marshaler
interface,
and the translator methods will use this over a more generic representation:
func (r *RGBA) MarshalJSON() ([]byte, error) {
b := *r
return []byte(fmt.Sprintf("[%d, %d, %d, %d]", b[0], b[1], b[2], b[3])), nil
}