-
Notifications
You must be signed in to change notification settings - Fork 2
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
. The length of the given []byte
will always be 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 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 if its concrete type, you must annotate the field with "extension"
directly:
type Thing struct {
Num Numbers `msgp:"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/dchenk/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 extension.
type RGBA [4]byte
// Here, we pick a 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 values of the bytes into slice 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 `msgp:"height`
Width int `msgp:"width"`
Color RGBA `msgp:"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
}