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.

121 lines
3.6 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.
  26. var registry = struct {
  27. types map[string]reflect.Type
  28. }{types: make(map[string]reflect.Type)}
  29. // register adds v to the type registry. It reports an error if the tag
  30. // returned by v is already registered.
  31. func register(v Tagged) error {
  32. tag := v.TypeTag()
  33. if t, ok := registry.types[tag]; ok {
  34. return fmt.Errorf("type tag %q already registered to %v", tag, t)
  35. }
  36. registry.types[tag] = reflect.TypeOf(v)
  37. return nil
  38. }
  39. // MustRegister adds v to the type registry. It will panic if the tag returned
  40. // by v is already registered. This function is meant for use during program
  41. // initialization.
  42. func MustRegister(v Tagged) {
  43. if err := register(v); err != nil {
  44. panic(err)
  45. }
  46. }
  47. type wrapper struct {
  48. Type string `json:"type"`
  49. Value json.RawMessage `json:"value"`
  50. }
  51. // Marshal marshals a JSON wrapper object containing v. If v == nil, Marshal
  52. // returns the JSON "null" value without error.
  53. func Marshal(v Tagged) ([]byte, error) {
  54. if v == nil {
  55. return []byte("null"), nil
  56. }
  57. data, err := json.Marshal(v)
  58. if err != nil {
  59. return nil, err
  60. }
  61. return json.Marshal(wrapper{
  62. Type: v.TypeTag(),
  63. Value: data,
  64. })
  65. }
  66. // Unmarshal unmarshals a JSON wrapper object into v. It reports an error if
  67. // the data do not encode a valid wrapper object, if the wrapper's type tag is
  68. // not registered with jsontypes, or if the resulting value is not compatible
  69. // with the type of v.
  70. func Unmarshal(data []byte, v interface{}) error {
  71. // Verify that the target is some kind of pointer.
  72. target := reflect.ValueOf(v)
  73. if target.Kind() != reflect.Ptr {
  74. return fmt.Errorf("target %T is not a pointer", v)
  75. } else if target.IsZero() {
  76. return fmt.Errorf("target is a nil %T", v)
  77. }
  78. baseType := target.Type().Elem()
  79. if isNull(data) {
  80. target.Elem().Set(reflect.Zero(baseType))
  81. return nil
  82. }
  83. var w wrapper
  84. dec := json.NewDecoder(bytes.NewReader(data))
  85. dec.DisallowUnknownFields()
  86. if err := dec.Decode(&w); err != nil {
  87. return fmt.Errorf("invalid type wrapper: %w", err)
  88. }
  89. typ, ok := registry.types[w.Type]
  90. if !ok {
  91. return fmt.Errorf("unknown type tag for %T: %q", v, w.Type)
  92. }
  93. if typ.AssignableTo(baseType) {
  94. // ok: registered type is directly assignable to the target
  95. } else if typ.Kind() == reflect.Ptr && typ.Elem().AssignableTo(baseType) {
  96. typ = typ.Elem()
  97. // ok: registered type is a pointer to a value assignable to the target
  98. } else {
  99. return fmt.Errorf("type %v is not assignable to %v", typ, baseType)
  100. }
  101. obj := reflect.New(typ) // we need a pointer to unmarshal
  102. if err := json.Unmarshal(w.Value, obj.Interface()); err != nil {
  103. return fmt.Errorf("decoding wrapped value: %w", err)
  104. }
  105. target.Elem().Set(obj.Elem())
  106. return nil
  107. }
  108. // isNull reports true if data is empty or is the JSON "null" value.
  109. func isNull(data []byte) bool {
  110. return len(data) == 0 || bytes.Equal(data, []byte("null"))
  111. }