mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-25 02:04:38 +00:00
172 lines
5.2 KiB
Go
172 lines
5.2 KiB
Go
![]() |
// 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
|
||
|
}
|