pkg/roachprod/roachprodutil/codec/README.md
The codec package provides a way of serializing and deserializing data structures, with YAML, that contain interfaces (referred to as dynamic types in this package)
For example:
struct Foo {
Bar interface{}
}
Typically, this would serialize without incident, using the standard YAML
library, and whichever concrete data Bar holds will be serialized.
However, challenges emerge during deserialization back to the original
structure. Without type information, the decoder cannot determine what concrete
type Bar originally held, resulting in deserialization to generic containers
like map[string]interface{}.
Ideally, we want to preserve the original type information, enabling us to maintain the polymorphic behavior associated with the original interface implementation.
Consider the following example types:
type (
Animal interface {
codec.DynamicType // Requierd for dynamic type serialization
Speak() string
}
Dog struct {
Name string
}
Cat struct {
Name string
}
)
func (d Dog) Speak() string {
return d.Name + " woofs"
}
func (c Cat) Speak() string {
return c.Name + " meows"
}
The codec.DynamicType should be added to any type that is to be serialized
as a dynamic type.
In order for the codec package decoder to recognize the types, they must be
registered and implement the codec.DynamicType. All concrete types that
implement codec.DynamicType must be registered.
A custom TypeName can be provided for each type, but it's preferred to use the
codec.ResolveTypeName function to generate a unique name for each type.
func init() {
codec.Register(new(Dog))
codec.Register(new(Cat))
}
func (d Dog) GetTypeName() codec.TypeName {
return codec.ResolveTypeName(d)
}
func (c Cat) GetTypeName() codec.TypeName {
return codec.ResolveTypeName(c)
}
Example of a structure ready to be serialized:
type struct Data {
animals codec.ListWrapper[Animal]
favouriteAnimal codec.Wrapper[Animal]
extinctAnimal codec.Wrapper[*Animal] // Pointers can be used as well
}
The structure can then be serialized and deserialized as usual:
var data Data
// ...
buf, err := yaml.Marshal(data)
// ...
dec := yaml.NewDecoder(bytes.NewReader(buf))
dec.KnownFields(true)
err = dec.Decode(&data)
The wrappers provide a convenient Get method that retrieves the underlying
value with proper type information preserved, leveraging Go's generics system to
ensure type safety without manual type assertions.
var animal Animal = data.favouriteAnimal.Get()
var animals []Animal = data.animals.Get()
data.extinctAnimal.Get().Speak()