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

  1. // Package jsontypes supports decoding for interface types whose concrete
  2. // implementations need to be stored as JSON. To do this, concrete values are
  3. // packaged in wrapper objects having the form:
  4. //
  5. // {
  6. // "type": "<type-tag>",
  7. // "value": <json-encoding-of-value>
  8. // }
  9. //
  10. // This package provides a registry for type tag strings and functions to
  11. // encode and decode wrapper objects.
  12. package jsontypes
  13. import (
  14. "bytes"
  15. "encoding/json"
  16. "fmt"
  17. "reflect"
  18. )
  19. // The Tagged interface must be implemented by a type in order to register it
  20. // with the jsontypes package. The TypeTag method returns a string label that
  21. // is used to distinguish objects of that type.
  22. type Tagged interface {
  23. TypeTag() string
  24. }
  25. // registry records the mapping from type tags to value types. Values in this
  26. // map must be normalized to non-pointer types.
  27. var registry = struct {
  28. types map[string]reflect.Type
  29. }{types: make(map[string]reflect.Type)}
  30. // register adds v to the type registry. It reports an error if the tag
  31. // returned by v is already registered.
  32. func register(v Tagged) error {
  33. tag := v.TypeTag()
  34. if t, ok := registry.types[tag]; ok {
  35. return fmt.Errorf("type tag %q already registered to %v", tag, t)
  36. }
  37. typ := reflect.TypeOf(v)
  38. if typ.Kind() == reflect.Ptr {
  39. typ = typ.Elem()
  40. }
  41. registry.types[tag] = typ
  42. return nil
  43. }
  44. // MustRegister adds v to the type registry. It will panic if the tag returned
  45. // by v is already registered. This function is meant for use during program
  46. // initialization.
  47. func MustRegister(v Tagged) {
  48. if err := register(v); err != nil {
  49. panic(err)
  50. }
  51. }
  52. type wrapper struct {
  53. Type string `json:"type"`
  54. Value json.RawMessage `json:"value"`
  55. }
  56. // Marshal marshals a JSON wrapper object containing v. If v == nil, Marshal
  57. // returns the JSON "null" value without error.
  58. func Marshal(v Tagged) ([]byte, error) {
  59. if v == nil {
  60. return []byte("null"), nil
  61. }
  62. data, err := json.Marshal(v)
  63. if err != nil {
  64. return nil, err
  65. }
  66. return json.Marshal(wrapper{
  67. Type: v.TypeTag(),
  68. Value: data,
  69. })
  70. }
  71. // Unmarshal unmarshals a JSON wrapper object into v. It reports an error if
  72. // the data do not encode a valid wrapper object, if the wrapper's type tag is
  73. // not registered with jsontypes, or if the resulting value is not compatible
  74. // with the type of v.
  75. func Unmarshal(data []byte, v interface{}) error {
  76. // Verify that the target is some kind of pointer.
  77. target := reflect.ValueOf(v)
  78. if target.Kind() != reflect.Ptr {
  79. return fmt.Errorf("target %T is not a pointer", v)
  80. }
  81. var w wrapper
  82. dec := json.NewDecoder(bytes.NewReader(data))
  83. dec.DisallowUnknownFields()
  84. if err := dec.Decode(&w); err != nil {
  85. return fmt.Errorf("invalid type wrapper: %w", err)
  86. }
  87. typ, ok := registry.types[w.Type]
  88. if !ok {
  89. return fmt.Errorf("unknown type tag: %q", w.Type)
  90. } else if !typ.AssignableTo(target.Elem().Type()) {
  91. return fmt.Errorf("type %v not assignable to %T", typ, v)
  92. }
  93. obj := reflect.New(typ)
  94. if err := json.Unmarshal(w.Value, obj.Interface()); err != nil {
  95. return fmt.Errorf("decoding wrapped value: %w", err)
  96. }
  97. target.Elem().Set(obj.Elem())
  98. return nil
  99. }