--- /dev/null
+package data
+
+import (
+ "bytes"
+ "encoding/json"
+ "reflect"
+
+ "github.com/pkg/errors"
+)
+
+type jsonMapper struct {
+ kindToType map[string]reflect.Type
+ typeToKind map[reflect.Type]string
+}
+
+func newJsonMapper(base interface{}) *jsonMapper {
+ return &jsonMapper{
+ kindToType: map[string]reflect.Type{},
+ typeToKind: map[reflect.Type]string{},
+ }
+}
+
+// ToJSON is a convenience method to serialize with encoding/json
+func ToJSON(o interface{}) ([]byte, error) {
+ d, err := json.MarshalIndent(o, "", " ")
+ return d, errors.WithStack(err)
+}
+
+// FromJSON is a convenience method to deserialize with encoding/json
+func FromJSON(d []byte, o interface{}) error {
+ return errors.WithStack(
+ json.Unmarshal(d, o))
+}
+
+// registerImplementation allows you to register multiple concrete types.
+//
+// Returns itself to allow calls to be chained
+func (m *jsonMapper) registerImplementation(data interface{}, kind string, b byte) {
+ typ := reflect.TypeOf(data)
+ m.kindToType[kind] = typ
+ m.typeToKind[typ] = kind
+}
+
+// getTarget returns a pointer to an allocated object of the proper kind
+func (m *jsonMapper) getTarget(kind string) (interface{}, error) {
+ typ, ok := m.kindToType[kind]
+ if !ok {
+ return nil, errors.Errorf("Unmarshaling into unknown type: %s", kind)
+ }
+ target := reflect.New(typ).Interface()
+ return target, nil
+}
+
+func (m *jsonMapper) getKind(obj interface{}) (string, error) {
+ typ := reflect.TypeOf(obj)
+ kind, ok := m.typeToKind[typ]
+ if !ok {
+ return "", errors.Errorf("Marshalling from unknown type: %#v", obj)
+ }
+ return kind, nil
+}
+
+// FromJSON will deserialize the output of ToJSON for every registered
+// implementation of the interface
+func (m *jsonMapper) FromJSON(data []byte) (interface{}, error) {
+ // handle null specially
+ if bytes.Equal(data, []byte("null")) {
+ return nil, nil
+ }
+ e := envelope{
+ Data: &json.RawMessage{},
+ }
+ err := json.Unmarshal(data, &e)
+ if err != nil {
+ return nil, err
+ }
+ // switch on the type, then unmarshal into that
+ bytes := *e.Data.(*json.RawMessage)
+ res, err := m.getTarget(e.Kind)
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(bytes, &res)
+ // getTarget returned a pointer for Unmarshall, now dereference it
+ res = reflect.ValueOf(res).Elem().Interface()
+ return res, err
+}
+
+// ToJson will serialize a registered implementation into a format like:
+// {
+// "type": "foo",
+// "data": {
+// "name": "dings"
+// }
+// }
+// this allows us to properly deserialize with FromJSON
+func (m *jsonMapper) ToJSON(data interface{}) ([]byte, error) {
+ // handle null specially
+ if data == nil {
+ return []byte("null"), nil
+ }
+ raw, err := json.Marshal(data)
+ if err != nil {
+ return nil, errors.WithStack(err)
+ }
+ kind, err := m.getKind(data)
+ if err != nil {
+ return nil, err
+ }
+ msg := json.RawMessage(raw)
+ e := envelope{
+ Kind: kind,
+ Data: &msg,
+ }
+ return json.Marshal(e)
+}
+
+// envelope lets us switch on type
+type envelope struct {
+ Kind string `json:"type"`
+ Data interface{} `json:"data"`
+}