// 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": "", // "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. 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) } registry.types[tag] = reflect.TypeOf(v) 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) } else if target.IsZero() { return fmt.Errorf("target is a nil %T", v) } baseType := target.Type().Elem() if isNull(data) { target.Elem().Set(reflect.Zero(baseType)) return nil } 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 for %T: %q", v, w.Type) } if typ.AssignableTo(baseType) { // ok: registered type is directly assignable to the target } else if typ.Kind() == reflect.Ptr && typ.Elem().AssignableTo(baseType) { typ = typ.Elem() // ok: registered type is a pointer to a value assignable to the target } else { return fmt.Errorf("type %v is not assignable to %v", typ, baseType) } obj := reflect.New(typ) // we need a pointer to unmarshal 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 } // isNull reports true if data is empty or is the JSON "null" value. func isNull(data []byte) bool { return len(data) == 0 || bytes.Equal(data, []byte("null")) }