c217e3c87a
The following changes were required: * integration/build: progressui's signature changed in |
||
---|---|---|
.. | ||
.bouncer.yaml | ||
.gitignore | ||
.golangci.yaml | ||
chain.go | ||
CONTRIBUTING.md | ||
converter.go | ||
LICENSE | ||
Makefile | ||
README.md |
Go struct
Converter
A library for converting between Go structs.
chain := converter.NewChain(V1{}, V2{}, V3{})
chain.Convert(myV1struct, &myV3struct)
Details
At its core, this library provides a Convert
function, which automatically
handles converting fields with the same name, and "convertable"
types. Some examples are:
string
->string
string
->*string
int
->string
string
->[]string
The automatic conversions are implemented when there is an obvious way to convert between the types. A lot more automatic conversions happen -- see the converter tests for a more comprehensive list of what is currently supported.
Not everything can be handled automatically, however, so there is also
a ConvertFrom
interface any struct in the graph can implement to
perform custom conversion, similar to how the stdlib MarshalJSON
and
UnmarshalJSON
would be implemented.
Additionally, and maybe most importantly, there is a converter.Chain
available,
which orchestrates conversions between multiple versions of structs. This could
be thought of similar to database migrations: given a starting struct and a target
struct, the chain.Convert
function iterates through every intermediary migration
in order to arrive at the target struct.
Basic Usage
To illustrate usage we'll start with a few basic structs, some of which
implement the ConvertFrom
interface due to breaking changes:
// --------- V1 struct definition below ---------
type V1 struct {
Name string
OldField string
}
// --------- V2 struct definition below ---------
type V2 struct {
Name string
NewField string // this was a renamed field
}
func (to *V2) ConvertFrom(from interface{}) error {
if from, ok := from.(V1); ok { // forward migration
to.NewField = from.OldField
}
return nil
}
// --------- V3 struct definition below ---------
type V3 struct {
Name []string
FinalField []string // this field was renamed and the type was changed
}
func (to *V3) ConvertFrom(from interface{}) error {
if from, ok := from.(V2); ok { // forward migration
to.FinalField = []string{from.NewField}
}
return nil
}
Given these type definitions, we can easily set up a conversion chain like this:
chain := converter.NewChain(V1{}, V2{}, V3{})
This chain can then be used to convert from an older version to a newer
version. This is because our ConvertFrom
definitions are only handling
forward migrations.
This chain can be used to convert from a V1
struct to a V3
struct easily,
like this:
v1 := // somehow get a populated v1 struct
v3 := V3{}
chain.Convert(v1, &v3)
Since we've defined our chain as V1
→ V2
→ V3
, the chain will execute
conversions to all intermediary structs (V2
, in this case) and ultimately end
when we've populated the v3
instance.
Note we haven't needed to define any conversions on the Name
field of any structs
since this one is convertible between structs: string
→ string
→ []string
.
Backwards Migrations
If we wanted to also provide backwards migrations, we could also easily add a case
to the ConvertFrom
methods. The whole set of structs would look something like this:
// --------- V1 struct definition below ---------
type V1 struct {
Name string
OldField string
}
func (to *V1) ConvertFrom(from interface{}) error {
if from, ok := from.(V2); ok { // backward migration
to.OldField = from.NewField
}
return nil
}
// --------- V2 struct definition below ---------
type V2 struct {
Name string
NewField string
}
func (to *V2) ConvertFrom(from interface{}) error {
if from, ok := from.(V1); ok { // forward migration
to.NewField = from.OldField
}
if from, ok := from.(V3); ok { // backward migration
to.NewField = from.FinalField[0]
}
return nil
}
// --------- V3 struct definition below ---------
type V3 struct {
Name []string
FinalField []string
}
func (to *V3) ConvertFrom(from interface{}) error {
if from, ok := from.(V2); ok { // forward migration
to.FinalField = []string{from.NewField}
}
return nil
}
At this point we could convert in either direction, for example a
V3
struct could convert to a V1
struct, with the caveat that there
may be data loss, as might need to happen due to changes in the data shapes.
Contributing
If you would like to contribute to this repository, please see the CONTRIBUTING.md.