Documentation ¶
Overview ¶
Package goalesce is a library for copying and merging objects in Go.
Read the project's README file for more information:
On pkg.go.dev: https://pkg.go.dev/github.com/adutra/goalesce#section-readme
On GitHub: https://github.com/adutra/goalesce#readme
Example ¶
package main import ( "encoding/json" "fmt" "reflect" "github.com/adutra/goalesce" ) type User struct { ID int Name string Age int } type Actor struct { ID int Name string } type Movie struct { Name string Description string Actors []Actor `goalesce:"id:ID"` Tags []string `goalesce:"union"` Labels map[string]string `goalesce:"atomic"` } type Bird interface { Chirp() } type Duck struct { Name string } func (d *Duck) Chirp() { println("quack") } type Goose struct { Name string } func (d *Goose) Chirp() { println("honk") } func main() { var v, copied interface{} // Copying immutable values: the "copy" operation is a no-op v = "abc" copied, _ = goalesce.DeepCopy(v) fmt.Printf("DeepCopy(%+v) = %+v\n", v, copied) // Copying structs: fields are deep-copied v = User{ID: 1, Name: "Alice"} copied, _ = goalesce.DeepCopy(v) fmt.Printf("DeepCopy(%+v) = %+v\n", v, copied) // Copying pointers: the values pointed to are deep-copied, the pointers have different addresses v = &User{ID: 1, Name: "Alice"} copied, _ = goalesce.DeepCopy(v) fmt.Printf("DeepCopy(%+v) = %+v\n", v, copied) // Copying maps: keys and values are deep-copied, the maps point to different addresses v = map[int]string{1: "a", 2: "b"} copied, _ = goalesce.DeepCopy(v) fmt.Printf("DeepCopy(%+v) = %+v\n", v, copied) // Copying slices: elements are deep-copied, the slices point to different addresses v = []int{1, 2} copied, _ = goalesce.DeepCopy(v) fmt.Printf("DeepCopy(%+v) = %+v\n", v, copied) // Copying interfaces v = Bird(&Duck{Name: "Donald"}) copied, _ = goalesce.DeepCopy(v) fmt.Printf("DeepCopy(%+v) = %+v\n", v, copied) var v1, v2, merged interface{} // Merging immutable values: the "merge" operation returns v2 if it is non-zero, otherwise v1 v1 = "abc" v2 = "def" merged, _ = goalesce.DeepMerge(v1, v2) fmt.Printf("DeepMerge(%+v, %+v) = %+v\n", v1, v2, merged) v1 = 1 v2 = 0 // 0 is the zero-value for ints: v1 will be returned merged, _ = goalesce.DeepMerge(v1, v2) fmt.Printf("DeepMerge(%+v, %+v) = %+v\n", v1, v2, merged) // Merging structs v1 = User{ID: 1, Name: "Alice"} v2 = User{ID: 1, Age: 20} merged, _ = goalesce.DeepMerge(v1, v2) fmt.Printf("DeepMerge(%+v, %+v) = %+v\n", v1, v2, merged) // Merging pointers v1 = &User{ID: 1, Name: "Alice"} v2 = &User{ID: 1, Age: 20} merged, _ = goalesce.DeepMerge(v1, v2) fmt.Printf("DeepMerge(%+v, %+v) = %+v\n", v1, v2, merged) // Merging maps v1 = map[int]string{1: "a", 2: "b"} v2 = map[int]string{2: "c", 3: "d"} merged, _ = goalesce.DeepMerge(v1, v2) fmt.Printf("DeepMerge(%+v, %+v) = %+v\n", v1, v2, merged) // Merging interfaces v1 = Bird(&Duck{Name: "Donald"}) v2 = Bird(&Duck{Name: "Scrooge"}) merged, _ = goalesce.DeepMerge(v1, v2) fmt.Printf("DeepMerge(%+v, %+v) = %+v\n", v1, v2, merged) // Merging interfaces: different implementations are not supported v1 = Bird(&Duck{Name: "Donald"}) v2 = Bird(&Goose{Name: "Scrooge"}) merged, err := goalesce.DeepMerge(v1, v2) fmt.Printf("DeepMerge(%+v, %+v) = %+v, %v\n", v1, v2, merged, err) // Merging slices with default atomic semantics v1 = []int{1, 2} v2 = []int{2, 3} merged, _ = goalesce.DeepMerge(v1, v2) fmt.Printf("DeepMerge(%+v, %+v) = %+v\n", v1, v2, merged) v1 = []int{1, 2} v2 = []int{} // empty slice is NOT a zero-value! merged, _ = goalesce.DeepMerge(v1, v2) fmt.Printf("DeepMerge(%+v, %+v) = %+v\n", v1, v2, merged) // Merging slices with empty slices treated as zero-value slices v1 = []int{1, 2} v2 = []int{} // empty slice will be considered zero-value merged, _ = goalesce.DeepMerge(v1, v2, goalesce.WithZeroEmptySliceMerge()) fmt.Printf("DeepMerge(%+v, %+v, ZeroEmptySlice) = %+v\n", v1, v2, merged) // Merging slices with set-union semantics v1 = []int{1, 2} v2 = []int{2, 3} merged, _ = goalesce.DeepMerge(v1, v2, goalesce.WithDefaultSliceSetUnionMerge()) fmt.Printf("DeepMerge(%+v, %+v, SetUnion) = %+v\n", v1, v2, merged) // Merging slices with list-append semantics v1 = []int{1, 2} v2 = []int{2, 3} merged, _ = goalesce.DeepMerge(v1, v2, goalesce.WithDefaultSliceListAppendMerge()) fmt.Printf("DeepMerge(%+v, %+v, ListAppend) = %+v\n", v1, v2, merged) // Merging slices with merge-by-index semantics v1 = []int{1, 2, 3} v2 = []int{-1, -2} merged, _ = goalesce.DeepMerge(v1, v2, goalesce.WithDefaultSliceMergeByIndex()) fmt.Printf("DeepMerge(%+v, %+v, MergeByIndex) = %+v\n", v1, v2, merged) // Merging slices with merge-by-id semantics, merge key = field User.ID v1 = []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}} v2 = []User{{ID: 2, Age: 30}, {ID: 1, Age: 20}} merged, _ = goalesce.DeepMerge(v1, v2, goalesce.WithSliceMergeByID(reflect.TypeOf([]User{}), "ID")) fmt.Printf("DeepMerge(%+v, %+v, MergeByID) = %+v\n", v1, v2, merged) // Merging structs with custom field strategies v1 = Movie{ Name: "The Matrix", Description: "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers.", Actors: []Actor{ {ID: 1, Name: "Keanu Reeves"}, {ID: 2, Name: "Laurence Fishburne"}, {ID: 3, Name: "Carrie-Anne Moss"}, }, Tags: []string{"sci-fi", "action"}, Labels: map[string]string{ "producer": "Wachowski Brothers", }, } v2 = Movie{ Name: "The Matrix", Actors: []Actor{ {ID: 2, Name: "Laurence Fishburne"}, {ID: 3, Name: "Carrie-Anne Moss"}, {ID: 4, Name: "Hugo Weaving"}, }, Tags: []string{"action", "fantasy"}, Labels: map[string]string{ "director": "Wachowski Brothers", }, } merged, _ = goalesce.DeepMerge(v1, v2) jsn, _ := json.MarshalIndent(merged, "", " ") fmt.Printf("Merged movie:\n%+v\n", string(jsn)) }
Output: DeepCopy(abc) = abc DeepCopy({ID:1 Name:Alice Age:0}) = {ID:1 Name:Alice Age:0} DeepCopy(&{ID:1 Name:Alice Age:0}) = &{ID:1 Name:Alice Age:0} DeepCopy(map[1:a 2:b]) = map[1:a 2:b] DeepCopy([1 2]) = [1 2] DeepCopy(&{Name:Donald}) = &{Name:Donald} DeepMerge(abc, def) = def DeepMerge(1, 0) = 1 DeepMerge({ID:1 Name:Alice Age:0}, {ID:1 Name: Age:20}) = {ID:1 Name:Alice Age:20} DeepMerge(&{ID:1 Name:Alice Age:0}, &{ID:1 Name: Age:20}) = &{ID:1 Name:Alice Age:20} DeepMerge(map[1:a 2:b], map[2:c 3:d]) = map[1:a 2:c 3:d] DeepMerge(&{Name:Donald}, &{Name:Scrooge}) = &{Name:Scrooge} DeepMerge(&{Name:Donald}, &{Name:Scrooge}) = <nil>, types do not match: *goalesce_test.Duck != *goalesce_test.Goose DeepMerge([1 2], [2 3]) = [2 3] DeepMerge([1 2], []) = [] DeepMerge([1 2], [], ZeroEmptySlice) = [1 2] DeepMerge([1 2], [2 3], SetUnion) = [1 2 3] DeepMerge([1 2], [2 3], ListAppend) = [1 2 2 3] DeepMerge([1 2 3], [-1 -2], MergeByIndex) = [-1 -2 3] DeepMerge([{ID:1 Name:Alice Age:0} {ID:2 Name:Bob Age:0}], [{ID:2 Name: Age:30} {ID:1 Name: Age:20}], MergeByID) = [{ID:1 Name:Alice Age:20} {ID:2 Name:Bob Age:30}] Merged movie: { "Name": "The Matrix", "Description": "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers.", "Actors": [ { "ID": 1, "Name": "Keanu Reeves" }, { "ID": 2, "Name": "Laurence Fishburne" }, { "ID": 3, "Name": "Carrie-Anne Moss" }, { "ID": 4, "Name": "Hugo Weaving" } ], "Tags": [ "sci-fi", "action", "fantasy" ], "Labels": { "director": "Wachowski Brothers" } }
Index ¶
- Constants
- func DeepCopy[T any](o T, opts ...Option) (T, error)
- func DeepMerge[T any](o1, o2 T, opts ...Option) (T, error)
- func MustDeepCopy[T any](o T, opts ...Option) T
- func MustDeepMerge[T any](o1, o2 T, opts ...Option) T
- type DeepCopyFunc
- type DeepCopyFuncProvider
- type DeepMergeFunc
- type DeepMergeFuncProvider
- type Option
- func WithArrayMergeByIndex(arrayType reflect.Type) Option
- func WithAtomicCopy(t reflect.Type) Option
- func WithAtomicFieldMerge(structType reflect.Type, field string) Option
- func WithAtomicMerge(t reflect.Type) Option
- func WithDefaultArrayMergeByIndex() Option
- func WithDefaultSliceListAppendMerge() Option
- func WithDefaultSliceMergeByIndex() Option
- func WithDefaultSliceSetUnionMerge() Option
- func WithErrorOnCycle() Option
- func WithFieldListAppendMerge(structType reflect.Type, field string) Option
- func WithFieldMergeByID(structType reflect.Type, field string, key string) Option
- func WithFieldMergeByIndex(structType reflect.Type, field string) Option
- func WithFieldMergeByKeyFunc(structType reflect.Type, field string, mergeKeyFunc SliceMergeKeyFunc) Option
- func WithFieldMerger(structType reflect.Type, field string, merger DeepMergeFunc) Option
- func WithFieldMergerProvider(structType reflect.Type, field string, provider DeepMergeFuncProvider) Option
- func WithFieldSetUnionMerge(structType reflect.Type, field string) Option
- func WithSliceListAppendMerge(sliceType reflect.Type) Option
- func WithSliceMergeByID(sliceOfStructType reflect.Type, elemField string) Option
- func WithSliceMergeByIndex(sliceType reflect.Type) Option
- func WithSliceMergeByKeyFunc(sliceType reflect.Type, mergeKeyFunc SliceMergeKeyFunc) Option
- func WithSliceSetUnionMerge(sliceType reflect.Type) Option
- func WithTrileanMerge() Option
- func WithTypeCopier(t reflect.Type, copier DeepCopyFunc) Option
- func WithTypeCopierProvider(t reflect.Type, provider DeepCopyFuncProvider) Option
- func WithTypeMerger(t reflect.Type, merger DeepMergeFunc) Option
- func WithTypeMergerProvider(t reflect.Type, provider DeepMergeFuncProvider) Option
- func WithZeroEmptySliceMerge() Option
- type SliceMergeKeyFunc
Examples ¶
Constants ¶
const ( // MergeStrategyAtomic applies "atomic" semantics. MergeStrategyAtomic = "atomic" // MergeStrategyAppend applies "list-append" semantics. MergeStrategyAppend = "append" // MergeStrategyUnion applies "set-union" semantics. MergeStrategyUnion = "union" // MergeStrategyIndex applies "merge-by-index" semantics. MergeStrategyIndex = "index" // MergeStrategyID applies "merge-by-id" semantics. MergeStrategyID = "id" )
const MergeStrategyTag = "goalesce"
MergeStrategyTag is the struct tag used to specify the merge strategy to use for a struct field.
Variables ¶
This section is empty.
Functions ¶
func DeepCopy ¶
DeepCopy deep-copies the value and returns the copied value.
This function never modifies its inputs. It always returns an entirely newly-allocated value that shares no references with the inputs.
func DeepMerge ¶
DeepMerge merges the 2 values and returns the merged value.
When called with no options, the function uses the following default algorithm:
- If both values are nil, return nil.
- If one value is nil, return the other value.
- If both values are zero-values for the type, return the type's zero-value.
- If one value is a zero-value for the type, return the other value.
- Otherwise, the values are merged using the following rules:
- If both values are interfaces of same underlying types, merge the underlying values.
- If both values are pointers, merge the values pointed to.
- If both values are maps, merge the maps recursively, key by key.
- If both values are structs, merge the structs recursively, field by field.
- For other types (including slices), return the second value ("atomic" semantics)
This function never modifies its inputs. It always returns an entirely newly-allocated value that shares no references with the inputs.
Note that by default, slices are merged with atomic semantics, that is, the second slice overwrites the first one completely. It is possible to change this behavior and use list-append, set-union, or merge-by semantics. See Option.
This function returns an error if the values are not of the same type, or if the merge encounters an error.
func MustDeepCopy ¶
MustDeepCopy is like DeepCopy, but panics if the copy returns an error.
func MustDeepMerge ¶
MustDeepMerge is like DeepMerge, but panics if the merge returns an error.
Types ¶
type DeepCopyFunc ¶
DeepCopyFunc is a function for copying objects. A deep copy function is expected to abide by the general contract of DeepCopy and to copy the given value to a newly-allocated value, avoiding retaining references to passed objects. Note that the passed values can be zero-values, but will never be invalid values. The returned value must be of same type as the passed value. By convention, when the function returns an invalid value and a nil error, it is assumed that the function is delegating the copy to the main copy function. See examples for more.
type DeepCopyFuncProvider ¶
type DeepCopyFuncProvider func(global DeepCopyFunc) DeepCopyFunc
DeepCopyFuncProvider is a factory for DeepCopyFunc instances. It takes the main DeepCopyFunc instance as argument. It allows to create type copiers that are able to delegate the copy of nested values to that instance, instead of having to handle them internally. See examples for more.
type DeepMergeFunc ¶
DeepMergeFunc is a function for merging objects. A deep merge function is expected to abide by the general contract of DeepMerge and to merge the 2 values into a single value, favoring v2 over v1 in case of conflicts. Note that the passed values can be zero-values, but will never be invalid values. The passed values are guaranteed to be of the same type; the returned value must also be of that same type. By convention, when the function returns an invalid value and a nil error, it is assumed that the function is delegating the merge to the main merge function. See examples for more.
type DeepMergeFuncProvider ¶
type DeepMergeFuncProvider func(globalMerger DeepMergeFunc, globalCopier DeepCopyFunc) DeepMergeFunc
DeepMergeFuncProvider is a factory for DeepMergeFunc instances. It takes the main DeepMergeFunc and DeepCopyFunc instances as arguments. It allows to create type mergers that are able to delegate the merge and copy of nested values to those instances, instead of having to handle them internally. See examples for more.
type Option ¶
type Option func(c *coalescer)
Option is an option that can be passed to DeepCopy or DeepMerge to customize the function behavior.
func WithArrayMergeByIndex ¶
WithArrayMergeByIndex applies merge-by-index semantics to the given slice type. The given mergeKeyFunc will be used to extract the element merge key.
func WithAtomicCopy ¶
WithAtomicCopy causes the given type to be copied with atomic semantics, instead of its default copy semantics. When a non-zero value of this type is copied, the value is returned as is.
func WithAtomicFieldMerge ¶
WithAtomicFieldMerge causes the given field to be merged atomically, that is, with "atomic" semantics, instead of its default merge semantics. When 2 non-zero-values of this field are merged, the second value is returned as is. This is the programmatic equivalent of adding a `goalesce:atomic` struct tag to that field.
func WithAtomicMerge ¶
WithAtomicMerge causes the given type to be merged with atomic semantics, instead of its default merge semantics. When 2 non-zero-values of this type are merged, the second value is returned as is. Note that this option does not modify the copy behavior for the type; if atomic semantics are also needed when copying (which is usually the case), use both this option and WithAtomicCopy.
func WithDefaultArrayMergeByIndex ¶
func WithDefaultArrayMergeByIndex() Option
WithDefaultArrayMergeByIndex applies merge-by-index semantics to all arrays to be merged.
func WithDefaultSliceListAppendMerge ¶
func WithDefaultSliceListAppendMerge() Option
WithDefaultSliceListAppendMerge applies list-append merge semantics to all slices to be merged.
Example ¶
package main import ( "fmt" "reflect" "strings" "github.com/adutra/goalesce" ) func main() { { v1 := []int{1, 2} v2 := []int{2, 3} merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithDefaultSliceListAppendMerge()) fmt.Printf("DeepMerge(%+v, %+v, ListAppend) = %+v\n", v1, v2, merged) } { // slice of pointers intPtr := func(i int) *int { return &i } v1 := []*int{new(int), intPtr(0)} v2 := []*int{(*int)(nil), intPtr(1)} merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithDefaultSliceListAppendMerge()) fmt.Printf("DeepMerge(%+v, %+v, ListAppend) = %+v\n", printPtrSlice(v1), printPtrSlice(v2), printPtrSlice(merged)) } } func printPtrSlice(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } s := make([]string, v.Len()) for i := 0; i < v.Len(); i++ { s[i] = printPtr(v.Index(i).Interface()) } return fmt.Sprintf("[%v]", strings.Join(s, " ")) } func printPtr(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } return fmt.Sprintf("&%+v", v.Elem().Interface()) }
Output: DeepMerge([1 2], [2 3], ListAppend) = [1 2 2 3] DeepMerge([&0 &0], [*int(nil) &1], ListAppend) = [&0 &0 *int(nil) &1]
func WithDefaultSliceMergeByIndex ¶
func WithDefaultSliceMergeByIndex() Option
WithDefaultSliceMergeByIndex applies merge-by-index semantics to all slices to be merged.
Example ¶
package main import ( "fmt" "reflect" "strings" "github.com/adutra/goalesce" ) func main() { { v1 := []int{1, 2, 3} v2 := []int{-1, -2} merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithDefaultSliceMergeByIndex()) fmt.Printf("DeepMerge(%+v, %+v, MergeByIndex) = %+v\n", v1, v2, merged) } { // slice of pointers intPtr := func(i int) *int { return &i } v1 := []*int{intPtr(1), intPtr(2), intPtr(3)} v2 := []*int{nil, intPtr(-2)} merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithDefaultSliceMergeByIndex()) fmt.Printf("DeepMerge(%+v, %+v, MergeByIndex) = %+v\n", printPtrSlice(v1), printPtrSlice(v2), printPtrSlice(merged)) } } func printPtrSlice(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } s := make([]string, v.Len()) for i := 0; i < v.Len(); i++ { s[i] = printPtr(v.Index(i).Interface()) } return fmt.Sprintf("[%v]", strings.Join(s, " ")) } func printPtr(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } return fmt.Sprintf("&%+v", v.Elem().Interface()) }
Output: DeepMerge([1 2 3], [-1 -2], MergeByIndex) = [-1 -2 3] DeepMerge([&1 &2 &3], [*int(nil) &-2], MergeByIndex) = [&1 &-2 &3]
func WithDefaultSliceSetUnionMerge ¶
func WithDefaultSliceSetUnionMerge() Option
WithDefaultSliceSetUnionMerge applies set-union merge semantics to all slices to be merged. When the slice elements are pointers, this strategy dereferences the pointers and compare their targets. This strategy is fine for slices of simple types and pointers thereof, but it is not recommended for slices of complex types as the elements may not be fully comparable.
Example ¶
package main import ( "fmt" "reflect" "strings" "github.com/adutra/goalesce" ) func main() { { v1 := []int{1, 2} v2 := []int{2, 3} merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithDefaultSliceSetUnionMerge()) fmt.Printf("DeepMerge(%+v, %+v, SetUnion) = %+v\n", v1, v2, merged) } { // slice of pointers intPtr := func(i int) *int { return &i } v1 := []*int{new(int), intPtr(0)} // new(int) and intPtr(0) are equal and point both to 0 v2 := []*int{nil, intPtr(1)} // nil will be merged as the zero-value (0) merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithDefaultSliceSetUnionMerge()) fmt.Printf("DeepMerge(%+v, %+v, SetUnion) = %+v\n", printPtrSlice(v1), printPtrSlice(v2), printPtrSlice(merged)) } } func printPtrSlice(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } s := make([]string, v.Len()) for i := 0; i < v.Len(); i++ { s[i] = printPtr(v.Index(i).Interface()) } return fmt.Sprintf("[%v]", strings.Join(s, " ")) } func printPtr(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } return fmt.Sprintf("&%+v", v.Elem().Interface()) }
Output: DeepMerge([1 2], [2 3], SetUnion) = [1 2 3] DeepMerge([&0 &0], [*int(nil) &1], SetUnion) = [&0 &1]
func WithErrorOnCycle ¶
func WithErrorOnCycle() Option
WithErrorOnCycle instructs the operation to return an error when a cycle is detected. By default, cycles are replaced with a nil pointer.
func WithFieldListAppendMerge ¶
WithFieldListAppendMerge merges the given struct field with list-append semantics. The field must be of slice type. This is the programmatic equivalent of adding a `goalesce:append` struct tag to that field.
func WithFieldMergeByID ¶
WithFieldMergeByID merges the given struct field with merge-by-key semantics. The field must be of slice type. The slice element type must be of some other struct type, or a pointer thereto. The passed key must be a valid field name for that struct type and will be used to extract the slice element's merge key; therefore, that field should generally be a unique identifier or primary key for objects of this type. This is the programmatic equivalent of adding a `goalesce:id:key` struct tag to the struct field.
func WithFieldMergeByIndex ¶
WithFieldMergeByIndex merges the given struct field with merge-by-index semantics. The field must be of slice type. This is the programmatic equivalent of adding a `goalesce:index` struct tag to that field.
func WithFieldMergeByKeyFunc ¶
func WithFieldMergeByKeyFunc(structType reflect.Type, field string, mergeKeyFunc SliceMergeKeyFunc) Option
WithFieldMergeByKeyFunc merges the given struct field with merge-by-key semantics. The field must be of slice type. The slice element type must be of some other struct type, or a pointer thereto. The given SliceMergeKeyFunc will be used to extract the slice element's merge key; therefore, the field should generally be a unique identifier or primary key for objects of this type.
func WithFieldMerger ¶
func WithFieldMerger(structType reflect.Type, field string, merger DeepMergeFunc) Option
WithFieldMerger merges the given struct field with the given custom merger. This option does not allow the type merger to access the parent DeepMergeFunc instance being created. For that, use WithFieldMergerProvider instead.
func WithFieldMergerProvider ¶
func WithFieldMergerProvider(structType reflect.Type, field string, provider DeepMergeFuncProvider) Option
WithFieldMergerProvider merges the given struct field with a custom merger that will be obtained by calling the given provider function with the global DeepMergeFunc and DeepCopyFunc instances. This option allows the type merger to access those instances in order to delegate the merge and copy of nested objects. See ExampleWithFieldMergerProvider.
Example ¶
package main import ( "errors" "fmt" "reflect" "github.com/adutra/goalesce" ) type User struct { ID int Name string Age int } func main() { userMergerProvider := func(mainMerger goalesce.DeepMergeFunc, mainCopier goalesce.DeepCopyFunc) goalesce.DeepMergeFunc { return func(v1, v2 reflect.Value) (reflect.Value, error) { if v1.Int() == 1 { return reflect.Value{}, errors.New("user 1 has been deleted") } return mainMerger(v1, v2) // delegate to main merger } } { v1 := User{ID: 1, Name: "Alice"} v2 := User{ID: 1, Age: 20} merged, err := goalesce.DeepMerge(v1, v2, goalesce.WithFieldMergerProvider(reflect.TypeOf(User{}), "ID", userMergerProvider)) fmt.Printf("DeepMerge(%+v, %+v, WithFieldMergerProvider) = %+v, %v\n", v1, v2, merged, err) } { v1 := User{ID: 2, Name: "Bob"} v2 := User{ID: 2, Age: 30} merged, err := goalesce.DeepMerge(v1, v2, goalesce.WithFieldMergerProvider(reflect.TypeOf(User{}), "ID", userMergerProvider)) fmt.Printf("DeepMerge(%+v, %+v, WithFieldMergerProvider) = %+v, %v\n", v1, v2, merged, err) } }
Output: DeepMerge({ID:1 Name:Alice Age:0}, {ID:1 Name: Age:20}, WithFieldMergerProvider) = {ID:0 Name: Age:0}, user 1 has been deleted DeepMerge({ID:2 Name:Bob Age:0}, {ID:2 Name: Age:30}, WithFieldMergerProvider) = {ID:2 Name:Bob Age:30}, <nil>
func WithFieldSetUnionMerge ¶
WithFieldSetUnionMerge merges the given struct field with set-union semantics. The field must be of slice type. This is the programmatic equivalent of adding a `goalesce:union` struct tag to that field.
func WithSliceListAppendMerge ¶
WithSliceListAppendMerge applies list-append merge semantics to the given slice type.
func WithSliceMergeByID ¶
WithSliceMergeByID applies merge-by-key semantics to slices whose elements are of some struct type, or a pointer thereto. The passed field name will be used to extract the element's merge key; therefore, the field should generally be a unique identifier or primary key for objects of this type.
Example ¶
package main import ( "fmt" "reflect" "strings" "github.com/adutra/goalesce" ) type User struct { ID int Name string Age int } func main() { { v1 := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}} v2 := []User{{ID: 2, Age: 30}, {ID: 1, Age: 20}} merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithSliceMergeByID(reflect.TypeOf([]User{}), "ID")) fmt.Printf("DeepMerge(%+v, %+v, MergeByID) = %+v\n", v1, v2, merged) } { // slice of pointers v1 := []*User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}} v2 := []*User{{ID: 2, Age: 30}, {ID: 1, Age: 20}} merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithSliceMergeByID(reflect.TypeOf([]*User{}), "ID")) fmt.Printf("DeepMerge(%+v, %+v, MergeByID) = %+v\n", printPtrSlice(v1), printPtrSlice(v2), printPtrSlice(merged)) } } func printPtrSlice(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } s := make([]string, v.Len()) for i := 0; i < v.Len(); i++ { s[i] = printPtr(v.Index(i).Interface()) } return fmt.Sprintf("[%v]", strings.Join(s, " ")) } func printPtr(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } return fmt.Sprintf("&%+v", v.Elem().Interface()) }
Output: DeepMerge([{ID:1 Name:Alice Age:0} {ID:2 Name:Bob Age:0}], [{ID:2 Name: Age:30} {ID:1 Name: Age:20}], MergeByID) = [{ID:1 Name:Alice Age:20} {ID:2 Name:Bob Age:30}] DeepMerge([&{ID:1 Name:Alice Age:0} &{ID:2 Name:Bob Age:0}], [&{ID:2 Name: Age:30} &{ID:1 Name: Age:20}], MergeByID) = [&{ID:1 Name:Alice Age:20} &{ID:2 Name:Bob Age:30}]
func WithSliceMergeByIndex ¶
WithSliceMergeByIndex applies merge-by-index semantics to the given slice type. The given mergeKeyFunc will be used to extract the element merge key.
func WithSliceMergeByKeyFunc ¶
func WithSliceMergeByKeyFunc(sliceType reflect.Type, mergeKeyFunc SliceMergeKeyFunc) Option
WithSliceMergeByKeyFunc applies merge-by-key semantics to the given slice type. The given SliceMergeKeyFunc will be used to extract the element merge key.
Example ¶
package main import ( "fmt" "reflect" "strings" "github.com/adutra/goalesce" ) type User struct { ID int Name string Age int } func main() { { v1 := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}} v2 := []User{{ID: 2, Age: 30}, {ID: 1, Age: 20}} mergeKeyFunc := func(_ int, v reflect.Value) (reflect.Value, error) { return v.FieldByName("ID"), nil } merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithSliceMergeByKeyFunc(reflect.TypeOf([]User{}), mergeKeyFunc)) fmt.Printf("DeepMerge(%+v, %+v, MergeByKeyFunc) = %+v\n", v1, v2, merged) } { v1 := []*User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}} v2 := []*User{{ID: 2, Age: 30}, {ID: 1, Age: 20}} mergeKeyFunc := func(_ int, v reflect.Value) (reflect.Value, error) { return v.Elem().FieldByName("ID"), nil } merged, _ := goalesce.DeepMerge(v1, v2, goalesce.WithSliceMergeByKeyFunc(reflect.TypeOf([]*User{}), mergeKeyFunc)) fmt.Printf("DeepMerge(%+v, %+v, MergeByKeyFunc) = %+v\n", printPtrSlice(v1), printPtrSlice(v2), printPtrSlice(merged)) } } func printPtrSlice(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } s := make([]string, v.Len()) for i := 0; i < v.Len(); i++ { s[i] = printPtr(v.Index(i).Interface()) } return fmt.Sprintf("[%v]", strings.Join(s, " ")) } func printPtr(i interface{}) string { v := reflect.ValueOf(i) if v.IsNil() { return fmt.Sprintf("%T(nil)", i) } return fmt.Sprintf("&%+v", v.Elem().Interface()) }
Output: DeepMerge([{ID:1 Name:Alice Age:0} {ID:2 Name:Bob Age:0}], [{ID:2 Name: Age:30} {ID:1 Name: Age:20}], MergeByKeyFunc) = [{ID:1 Name:Alice Age:20} {ID:2 Name:Bob Age:30}] DeepMerge([&{ID:1 Name:Alice Age:0} &{ID:2 Name:Bob Age:0}], [&{ID:2 Name: Age:30} &{ID:1 Name: Age:20}], MergeByKeyFunc) = [&{ID:1 Name:Alice Age:20} &{ID:2 Name:Bob Age:30}]
func WithSliceSetUnionMerge ¶
WithSliceSetUnionMerge applies set-union merge semantics to the given slice type. When the slice elements are of a pointer type, this strategy dereferences the pointers and compare their targets. This strategy is fine for slices of simple types and pointers thereof, but it is not recommended for slices of complex types as the elements may not be fully comparable.
func WithTrileanMerge ¶
func WithTrileanMerge() Option
WithTrileanMerge causes all boolean pointers to be merged using a three-valued logic, instead of their default merge semantics. When this is enabled, boolean pointers will behave as if they were "trileans", that is, a type with 3 possible values: nil (its zero-value), false and true (contrary to booleans, with trileans false is NOT a zero-value). The merge of trileans obeys the following rules:
v1 v2 merged nil nil nil nil false false nil true true false nil false false false false false true true true nil true true false false true true true
The biggest difference with regular boolean pointers is that DeepMerge(&true, &false) will return &true for boolean pointers, while with trileans, it will return &false.
func WithTypeCopier ¶
func WithTypeCopier(t reflect.Type, copier DeepCopyFunc) Option
WithTypeCopier will defer the copy of the given type to the given custom copier. This option does not allow the type copier to access the global DeepCopyFunc instance. For that, use WithTypeCopierProvider instead.
Example ¶
package main import ( "fmt" "reflect" "github.com/adutra/goalesce" ) func main() { negatingCopier := func(v reflect.Value) (reflect.Value, error) { if v.Int() < 0 { return reflect.Value{}, nil // delegate to main copier } result := reflect.New(v.Type()).Elem() result.SetInt(-v.Int()) return result, nil } { v := 1 copied, err := goalesce.DeepCopy(v, goalesce.WithTypeCopier(reflect.TypeOf(v), negatingCopier)) fmt.Printf("DeepCopy(%+v, WithTypeCopier) = %+v, %v\n", v, copied, err) } { v := -1 copied, err := goalesce.DeepCopy(v, goalesce.WithTypeCopier(reflect.TypeOf(v), negatingCopier)) fmt.Printf("DeepCopy(%+v, WithTypeCopier) = %+v, %v\n", v, copied, err) } }
Output: DeepCopy(1, WithTypeCopier) = -1, <nil> DeepCopy(-1, WithTypeCopier) = -1, <nil>
func WithTypeCopierProvider ¶
func WithTypeCopierProvider(t reflect.Type, provider DeepCopyFuncProvider) Option
WithTypeCopierProvider will defer the copy of the given type to a custom copier that will be obtained by calling the given provider function with the global DeepCopyFunc instance. This option allows the type copier to access this instance in order to delegate the copy of nested objects. See ExampleWithTypeCopierProvider.
Example ¶
package main import ( "errors" "fmt" "reflect" "github.com/adutra/goalesce" ) type User struct { ID int Name string Age int } func main() { userCopierProvider := func(mainCopier goalesce.DeepCopyFunc) goalesce.DeepCopyFunc { return func(v reflect.Value) (reflect.Value, error) { if v.FieldByName("ID").Int() == 1 { return reflect.Value{}, errors.New("user 1 has been deleted") } result := reflect.New(v.Type()).Elem() id, err := mainCopier(v.FieldByName("ID")) if err != nil { return reflect.Value{}, err } result.FieldByName("ID").Set(id) name, err := mainCopier(v.FieldByName("Name")) if err != nil { return reflect.Value{}, err } result.FieldByName("Name").Set(name) return result, nil } } { v := User{ID: 1, Name: "Alice"} copied, err := goalesce.DeepCopy(v, goalesce.WithTypeCopierProvider(reflect.TypeOf(User{}), userCopierProvider)) fmt.Printf("DeepCopy(%+v, WithTypeCopier) = %+v, %v\n", v, copied, err) } { v := User{ID: 2, Name: "Bob"} copied, err := goalesce.DeepCopy(v, goalesce.WithTypeCopierProvider(reflect.TypeOf(User{}), userCopierProvider)) fmt.Printf("DeepCopy(%+v, WithTypeCopier) = %+v, %v\n", v, copied, err) } }
Output: DeepCopy({ID:1 Name:Alice Age:0}, WithTypeCopier) = {ID:0 Name: Age:0}, user 1 has been deleted DeepCopy({ID:2 Name:Bob Age:0}, WithTypeCopier) = {ID:2 Name:Bob Age:0}, <nil>
func WithTypeMerger ¶
func WithTypeMerger(t reflect.Type, merger DeepMergeFunc) Option
WithTypeMerger will defer the merge of the given type to the given custom merger. This option does not allow the type merger to access the global DeepMergeFunc instance. For that, use WithTypeMergerProvider instead.
Example ¶
package main import ( "fmt" "reflect" "github.com/adutra/goalesce" ) func main() { dividingMerger := func(v1, v2 reflect.Value) (reflect.Value, error) { if v2.Int() == 0 { return reflect.Value{}, nil // delegate to main merger } result := reflect.New(v1.Type()).Elem() result.SetInt(v1.Int() / v2.Int()) return result, nil } { v1 := 6 v2 := 2 merged, err := goalesce.DeepMerge(v1, v2, goalesce.WithTypeMerger(reflect.TypeOf(v1), dividingMerger)) fmt.Printf("DeepMerge(%+v, %+v, WithTypeMerger) = %+v, %v\n", v1, v2, merged, err) } { v1 := 1 v2 := 0 merged, err := goalesce.DeepMerge(v1, v2, goalesce.WithTypeMerger(reflect.TypeOf(v1), dividingMerger)) fmt.Printf("DeepMerge(%+v, %+v, WithTypeMerger) = %+v, %v\n", v1, v2, merged, err) } }
Output: DeepMerge(6, 2, WithTypeMerger) = 3, <nil> DeepMerge(1, 0, WithTypeMerger) = 1, <nil>
func WithTypeMergerProvider ¶
func WithTypeMergerProvider(t reflect.Type, provider DeepMergeFuncProvider) Option
WithTypeMergerProvider will defer the merge of the given type to a custom merger that will be obtained by calling the given provider function with the global DeepMergeFunc and DeepCopyFunc instances. This option allows the type merger to access those instances in order to delegate the merge and copy of nested objects. See ExampleWithTypeMergerProvider.
Example ¶
package main import ( "errors" "fmt" "reflect" "github.com/adutra/goalesce" ) type User struct { ID int Name string Age int } func main() { userMergerProvider := func(mainMerger goalesce.DeepMergeFunc, mainCopier goalesce.DeepCopyFunc) goalesce.DeepMergeFunc { return func(v1, v2 reflect.Value) (reflect.Value, error) { if v1.FieldByName("ID").Int() == 1 { return reflect.Value{}, errors.New("user 1 has been deleted") } result := reflect.New(v1.Type()).Elem() id, err := mainCopier(v1.FieldByName("ID")) if err != nil { return reflect.Value{}, err } result.FieldByName("ID").Set(id) name, err := mainMerger(v1.FieldByName("Name"), v2.FieldByName("Name")) if err != nil { return reflect.Value{}, err } result.FieldByName("Name").Set(name) age, err := mainMerger(v1.FieldByName("Age"), v2.FieldByName("Age")) if err != nil { return reflect.Value{}, err } result.FieldByName("Age").Set(age) return result, nil } } { v1 := User{ID: 1, Name: "Alice"} v2 := User{ID: 1, Age: 20} merged, err := goalesce.DeepMerge(v1, v2, goalesce.WithTypeMergerProvider(reflect.TypeOf(User{}), userMergerProvider)) fmt.Printf("DeepMerge(%+v, %+v, WithTypeMerger) = %+v, %v\n", v1, v2, merged, err) } { v1 := User{ID: 2, Name: "Bob"} v2 := User{ID: 2, Age: 30} merged, err := goalesce.DeepMerge(v1, v2, goalesce.WithTypeMergerProvider(reflect.TypeOf(User{}), userMergerProvider)) fmt.Printf("DeepMerge(%+v, %+v, WithTypeMerger) = %+v, %v\n", v1, v2, merged, err) } }
Output: DeepMerge({ID:1 Name:Alice Age:0}, {ID:1 Name: Age:20}, WithTypeMerger) = {ID:0 Name: Age:0}, user 1 has been deleted DeepMerge({ID:2 Name:Bob Age:0}, {ID:2 Name: Age:30}, WithTypeMerger) = {ID:2 Name:Bob Age:30}, <nil>
func WithZeroEmptySliceMerge ¶
func WithZeroEmptySliceMerge() Option
WithZeroEmptySliceMerge instructs the merger to consider empty slices as zero (nil) slices. This changes the default behavior: when merging a non-empty slice with an empty slice, normally the empty slice is returned, but with this option, the non-empty slice is returned.
type SliceMergeKeyFunc ¶
SliceMergeKeyFunc is a function that extracts a merge key from a slice element's index and value. The passed element may be the zero-value for the slice element type, but it will never be an invalid value. The returned merge key can be a zero-value, but cannot be invalid; moreover, it must be comparable as it will be stored internally in a temporary map during the merge.
var SliceIndex SliceMergeKeyFunc = func(index int, element reflect.Value) (key reflect.Value, err error) { return reflect.ValueOf(index), nil }
SliceIndex is a merge key func that returns the element indices as keys, thus achieving merge-by-index semantics. When using this func to do slice merges, the resulting slices will have their elements coalesced index by index.
var SliceUnion SliceMergeKeyFunc = func(index int, element reflect.Value) (key reflect.Value, err error) { return safeIndirect(element), nil }
SliceUnion is a merge key func that returns the elements themselves as keys, thus achieving set-union semantics. If the elements are pointers, they are dereferenced, which means that the set-union semantics will apply to the pointer targets, not to the pointers themselves. When using this func to do slice merges, the resulting slices will have no duplicate items (that is, items having the same merge key).