# Go `struct` Converter A library for converting between Go structs. ```go 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](converter_test.go) 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: ```go // --------- 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: ```go 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: ```go 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: ```go // --------- 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](CONTRIBUTING.md).