You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

109 lines
3.1 KiB

// Package jsontypes supports decoding for interface types whose concrete
// implementations need to be stored as JSON. To do this, concrete values are
// packaged in wrapper objects having the form:
//
// {
// "type": "<type-tag>",
// "value": <json-encoding-of-value>
// }
//
// This package provides a registry for type tag strings and functions to
// encode and decode wrapper objects.
package jsontypes
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
)
// The Tagged interface must be implemented by a type in order to register it
// with the jsontypes package. The TypeTag method returns a string label that
// is used to distinguish objects of that type.
type Tagged interface {
TypeTag() string
}
// registry records the mapping from type tags to value types. Values in this
// map must be normalized to non-pointer types.
var registry = struct {
types map[string]reflect.Type
}{types: make(map[string]reflect.Type)}
// register adds v to the type registry. It reports an error if the tag
// returned by v is already registered.
func register(v Tagged) error {
tag := v.TypeTag()
if t, ok := registry.types[tag]; ok {
return fmt.Errorf("type tag %q already registered to %v", tag, t)
}
typ := reflect.TypeOf(v)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
registry.types[tag] = typ
return nil
}
// MustRegister adds v to the type registry. It will panic if the tag returned
// by v is already registered. This function is meant for use during program
// initialization.
func MustRegister(v Tagged) {
if err := register(v); err != nil {
panic(err)
}
}
type wrapper struct {
Type string `json:"type"`
Value json.RawMessage `json:"value"`
}
// Marshal marshals a JSON wrapper object containing v. If v == nil, Marshal
// returns the JSON "null" value without error.
func Marshal(v Tagged) ([]byte, error) {
if v == nil {
return []byte("null"), nil
}
data, err := json.Marshal(v)
if err != nil {
return nil, err
}
return json.Marshal(wrapper{
Type: v.TypeTag(),
Value: data,
})
}
// Unmarshal unmarshals a JSON wrapper object into v. It reports an error if
// the data do not encode a valid wrapper object, if the wrapper's type tag is
// not registered with jsontypes, or if the resulting value is not compatible
// with the type of v.
func Unmarshal(data []byte, v interface{}) error {
// Verify that the target is some kind of pointer.
target := reflect.ValueOf(v)
if target.Kind() != reflect.Ptr {
return fmt.Errorf("target %T is not a pointer", v)
}
var w wrapper
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(&w); err != nil {
return fmt.Errorf("invalid type wrapper: %w", err)
}
typ, ok := registry.types[w.Type]
if !ok {
return fmt.Errorf("unknown type tag: %q", w.Type)
} else if !typ.AssignableTo(target.Elem().Type()) {
return fmt.Errorf("type %v not assignable to %T", typ, v)
}
obj := reflect.New(typ)
if err := json.Unmarshal(w.Value, obj.Interface()); err != nil {
return fmt.Errorf("decoding wrapped value: %w", err)
}
target.Elem().Set(obj.Elem())
return nil
}