Skip to content
This repository has been archived by the owner on May 8, 2019. It is now read-only.

Using Extensions

dchenk edited this page Apr 15, 2018 · 4 revisions

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 Extension interface

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()

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()

Len() should return the encoded length of the object, in bytes, given its current state.

MarshalBinaryTo([]byte)

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([]byte)

UnmarshalBinary should decode the value of the object from the supplied []byte.

Registering Extensions

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.)

Generating Code

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 { /* ... */ }

An Example

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
}