172 lines
5.2 KiB
Go
Raw Normal View History

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package jsonx contains helper types and functionality to use with
// [github.com/go-json-experiment/json], which is positioned to be
// merged into the Go standard library as [encoding/json/v2].
//
// See https://go.dev/issues/71497
package jsonx
import (
"errors"
"fmt"
"reflect"
"github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)
var (
errUnknownTypeName = errors.New("unknown type name")
errNonSingularValue = errors.New("dynamic value must only have exactly one member")
)
// MakeInterfaceCoders constructs a pair of marshal and unmarshal functions
// to serialize a Go interface type T. A bijective mapping for the set
// of concrete types that implement T is provided,
// where the key is a stable type name to use in the JSON representation,
// while the value is any value of a concrete type that implements T.
// By convention, only the zero value of concrete types is passed.
//
// The JSON representation for a dynamic value is a JSON object
// with a single member, where the member name is the type name,
// and the value is the JSON representation for the Go value.
// For example, the JSON serialization for a concrete type named Foo
// would be {"Foo": ...}, where ... is the JSON representation
// of the concrete value of the Foo type.
//
// Example instantiation:
//
// // Interface is a union type implemented by [FooType] and [BarType].
// type Interface interface { ... }
//
// var interfaceCoders = MakeInterfaceCoders(map[string]Interface{
// "FooType": FooType{},
// "BarType": (*BarType)(nil),
// })
//
// The pair of Marshal and Unmarshal functions can be used with the [json]
// package with either type-specified or caller-specified serialization.
// The result of this constructor is usually stored into a global variable.
//
// Example usage with type-specified serialization:
//
// // InterfaceWrapper is a concrete type that wraps [Interface].
// // It extends [Interface] to implement
// // [json.MarshalerTo] and [json.UnmarshalerFrom].
// type InterfaceWrapper struct{ Interface }
//
// func (w InterfaceWrapper) MarshalJSONTo(enc *jsontext.Encoder) error {
// return interfaceCoders.Marshal(enc, &w.Interface)
// }
//
// func (w *InterfaceWrapper) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
// return interfaceCoders.Unmarshal(dec, &w.Interface)
// }
//
// Example usage with caller-specified serialization:
//
// var opts json.Options = json.JoinOptions(
// json.WithMarshalers(json.MarshalToFunc(interfaceCoders.Marshal)),
// json.WithUnmarshalers(json.UnmarshalFromFunc(interfaceCoders.Unmarshal)),
// )
//
// var v Interface
// ... := json.Marshal(v, opts)
// ... := json.Unmarshal(&v, opts)
//
// The function panics if T is not a named interface kind,
// or if valuesByName contains distinct entries with the same concrete type.
func MakeInterfaceCoders[T any](valuesByName map[string]T) (c struct {
Marshal func(*jsontext.Encoder, *T) error
Unmarshal func(*jsontext.Decoder, *T) error
}) {
// Verify that T is a named interface.
switch t := reflect.TypeFor[T](); {
case t.Kind() != reflect.Interface:
panic(fmt.Sprintf("%v must be an interface kind", t))
case t.Name() == "":
panic(fmt.Sprintf("%v must be a named type", t))
}
// Construct a bijective mapping of names to types.
typesByName := make(map[string]reflect.Type)
namesByType := make(map[reflect.Type]string)
for name, value := range valuesByName {
t := reflect.TypeOf(value)
if t == nil {
panic(fmt.Sprintf("nil value for %s", name))
}
if name2, ok := namesByType[t]; ok {
panic(fmt.Sprintf("type %v cannot have multiple names %s and %v", t, name, name2))
}
typesByName[name] = t
namesByType[t] = name
}
// Construct the marshal and unmarshal functions.
c.Marshal = func(enc *jsontext.Encoder, val *T) error {
t := reflect.TypeOf(*val)
if t == nil {
return enc.WriteToken(jsontext.Null)
}
name := namesByType[t]
if name == "" {
return fmt.Errorf("Go type %v: %w", t, errUnknownTypeName)
}
if err := enc.WriteToken(jsontext.BeginObject); err != nil {
return err
}
if err := enc.WriteToken(jsontext.String(name)); err != nil {
return err
}
if err := json.MarshalEncode(enc, *val); err != nil {
return err
}
if err := enc.WriteToken(jsontext.EndObject); err != nil {
return err
}
return nil
}
c.Unmarshal = func(dec *jsontext.Decoder, val *T) error {
switch tok, err := dec.ReadToken(); {
case err != nil:
return err
case tok.Kind() == 'n':
var zero T
*val = zero // store nil interface value for JSON null
return nil
case tok.Kind() != '{':
return &json.SemanticError{JSONKind: tok.Kind(), GoType: reflect.TypeFor[T]()}
}
var v reflect.Value
switch tok, err := dec.ReadToken(); {
case err != nil:
return err
case tok.Kind() != '"':
return errNonSingularValue
default:
t := typesByName[tok.String()]
if t == nil {
return errUnknownTypeName
}
v = reflect.New(t)
}
if err := json.UnmarshalDecode(dec, v.Interface()); err != nil {
return err
}
*val = v.Elem().Interface().(T)
switch tok, err := dec.ReadToken(); {
case err != nil:
return err
case tok.Kind() != '}':
return errNonSingularValue
}
return nil
}
return c
}