|
@@ -12,10 +12,34 @@ import (
|
|
"reflect"
|
|
"reflect"
|
|
)
|
|
)
|
|
|
|
|
|
|
|
+func hasExportedField(dst reflect.Value) (exported bool) {
|
|
|
|
+ for i, n := 0, dst.NumField(); i < n; i++ {
|
|
|
|
+ field := dst.Type().Field(i)
|
|
|
|
+ if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
|
|
|
|
+ exported = exported || hasExportedField(dst.Field(i))
|
|
|
|
+ } else {
|
|
|
|
+ exported = exported || len(field.PkgPath) == 0
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type Config struct {
|
|
|
|
+ Overwrite bool
|
|
|
|
+ AppendSlice bool
|
|
|
|
+ Transformers Transformers
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type Transformers interface {
|
|
|
|
+ Transformer(reflect.Type) func(dst, src reflect.Value) error
|
|
|
|
+}
|
|
|
|
+
|
|
// Traverses recursively both values, assigning src's fields values to dst.
|
|
// Traverses recursively both values, assigning src's fields values to dst.
|
|
// The map argument tracks comparisons that have already been seen, which allows
|
|
// The map argument tracks comparisons that have already been seen, which allows
|
|
// short circuiting on recursive types.
|
|
// short circuiting on recursive types.
|
|
-func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) {
|
|
|
|
|
|
+func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
|
|
|
+ overwrite := config.Overwrite
|
|
|
|
+
|
|
if !src.IsValid() {
|
|
if !src.IsValid() {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
@@ -32,14 +56,31 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, ov
|
|
// Remember, remember...
|
|
// Remember, remember...
|
|
visited[h] = &visit{addr, typ, seen}
|
|
visited[h] = &visit{addr, typ, seen}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if config.Transformers != nil && !isEmptyValue(dst) {
|
|
|
|
+ if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
|
|
|
|
+ err = fn(dst, src)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
switch dst.Kind() {
|
|
switch dst.Kind() {
|
|
case reflect.Struct:
|
|
case reflect.Struct:
|
|
- for i, n := 0, dst.NumField(); i < n; i++ {
|
|
|
|
- if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, overwrite); err != nil {
|
|
|
|
- return
|
|
|
|
|
|
+ if hasExportedField(dst) {
|
|
|
|
+ for i, n := 0, dst.NumField(); i < n; i++ {
|
|
|
|
+ if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
|
|
|
|
+ dst.Set(src)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case reflect.Map:
|
|
case reflect.Map:
|
|
|
|
+ if dst.IsNil() && !src.IsNil() {
|
|
|
|
+ dst.Set(reflect.MakeMap(dst.Type()))
|
|
|
|
+ }
|
|
for _, key := range src.MapKeys() {
|
|
for _, key := range src.MapKeys() {
|
|
srcElement := src.MapIndex(key)
|
|
srcElement := src.MapIndex(key)
|
|
if !srcElement.IsValid() {
|
|
if !srcElement.IsValid() {
|
|
@@ -47,40 +88,99 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, ov
|
|
}
|
|
}
|
|
dstElement := dst.MapIndex(key)
|
|
dstElement := dst.MapIndex(key)
|
|
switch srcElement.Kind() {
|
|
switch srcElement.Kind() {
|
|
- case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
|
|
|
|
|
|
+ case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
|
|
if srcElement.IsNil() {
|
|
if srcElement.IsNil() {
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
fallthrough
|
|
fallthrough
|
|
default:
|
|
default:
|
|
|
|
+ if !srcElement.CanInterface() {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
|
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
|
case reflect.Struct:
|
|
case reflect.Struct:
|
|
fallthrough
|
|
fallthrough
|
|
case reflect.Ptr:
|
|
case reflect.Ptr:
|
|
fallthrough
|
|
fallthrough
|
|
case reflect.Map:
|
|
case reflect.Map:
|
|
- if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
|
|
|
|
|
|
+ srcMapElm := srcElement
|
|
|
|
+ dstMapElm := dstElement
|
|
|
|
+ if srcMapElm.CanInterface() {
|
|
|
|
+ srcMapElm = reflect.ValueOf(srcMapElm.Interface())
|
|
|
|
+ if dstMapElm.IsValid() {
|
|
|
|
+ dstMapElm = reflect.ValueOf(dstMapElm.Interface())
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+ case reflect.Slice:
|
|
|
|
+ srcSlice := reflect.ValueOf(srcElement.Interface())
|
|
|
|
+
|
|
|
|
+ var dstSlice reflect.Value
|
|
|
|
+ if !dstElement.IsValid() || dstElement.IsNil() {
|
|
|
|
+ dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
|
|
|
|
+ } else {
|
|
|
|
+ dstSlice = reflect.ValueOf(dstElement.Interface())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
|
|
|
|
+ dstSlice = srcSlice
|
|
|
|
+ } else if config.AppendSlice {
|
|
|
|
+ dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
|
|
|
|
+ }
|
|
|
|
+ dst.SetMapIndex(key, dstSlice)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) {
|
|
|
|
|
|
+ if dstElement.IsValid() && reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if srcElement.IsValid() && (overwrite || (!dstElement.IsValid() || isEmptyValue(dstElement))) {
|
|
if dst.IsNil() {
|
|
if dst.IsNil() {
|
|
dst.Set(reflect.MakeMap(dst.Type()))
|
|
dst.Set(reflect.MakeMap(dst.Type()))
|
|
}
|
|
}
|
|
dst.SetMapIndex(key, srcElement)
|
|
dst.SetMapIndex(key, srcElement)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ case reflect.Slice:
|
|
|
|
+ if !dst.CanSet() {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
|
|
|
|
+ dst.Set(src)
|
|
|
|
+ } else if config.AppendSlice {
|
|
|
|
+ dst.Set(reflect.AppendSlice(dst, src))
|
|
|
|
+ }
|
|
case reflect.Ptr:
|
|
case reflect.Ptr:
|
|
fallthrough
|
|
fallthrough
|
|
case reflect.Interface:
|
|
case reflect.Interface:
|
|
if src.IsNil() {
|
|
if src.IsNil() {
|
|
break
|
|
break
|
|
- } else if dst.IsNil() {
|
|
|
|
|
|
+ }
|
|
|
|
+ if src.Kind() != reflect.Interface {
|
|
|
|
+ if dst.IsNil() || overwrite {
|
|
|
|
+ if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
|
|
|
+ dst.Set(src)
|
|
|
|
+ }
|
|
|
|
+ } else if src.Kind() == reflect.Ptr {
|
|
|
|
+ if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ } else if dst.Elem().Type() == src.Type() {
|
|
|
|
+ if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ return ErrDifferentArgumentsTypes
|
|
|
|
+ }
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ if dst.IsNil() || overwrite {
|
|
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
|
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
|
dst.Set(src)
|
|
dst.Set(src)
|
|
}
|
|
}
|
|
- } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, overwrite); err != nil {
|
|
|
|
|
|
+ } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
default:
|
|
default:
|
|
@@ -91,30 +191,55 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, ov
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
-// Merge sets fields' values in dst from src if they have a zero
|
|
|
|
-// value of their type.
|
|
|
|
-// dst and src must be valid same-type structs and dst must be
|
|
|
|
-// a pointer to struct.
|
|
|
|
-// It won't merge unexported (private) fields and will do recursively
|
|
|
|
-// any exported field.
|
|
|
|
-func Merge(dst, src interface{}) error {
|
|
|
|
- return merge(dst, src, false)
|
|
|
|
|
|
+// Merge will fill any empty for value type attributes on the dst struct using corresponding
|
|
|
|
+// src attributes if they themselves are not empty. dst and src must be valid same-type structs
|
|
|
|
+// and dst must be a pointer to struct.
|
|
|
|
+// It won't merge unexported (private) fields and will do recursively any exported field.
|
|
|
|
+func Merge(dst, src interface{}, opts ...func(*Config)) error {
|
|
|
|
+ return merge(dst, src, opts...)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by
|
|
|
|
+// non-empty src attribute values.
|
|
|
|
+// Deprecated: use Merge(…) with WithOverride
|
|
|
|
+func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
|
|
|
+ return merge(dst, src, append(opts, WithOverride)...)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
|
|
|
|
+func WithTransformers(transformers Transformers) func(*Config) {
|
|
|
|
+ return func(config *Config) {
|
|
|
|
+ config.Transformers = transformers
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
|
|
|
|
+func WithOverride(config *Config) {
|
|
|
|
+ config.Overwrite = true
|
|
}
|
|
}
|
|
|
|
|
|
-func MergeWithOverwrite(dst, src interface{}) error {
|
|
|
|
- return merge(dst, src, true)
|
|
|
|
|
|
+// WithAppendSlice will make merge append slices instead of overwriting it
|
|
|
|
+func WithAppendSlice(config *Config) {
|
|
|
|
+ config.AppendSlice = true
|
|
}
|
|
}
|
|
|
|
|
|
-func merge(dst, src interface{}, overwrite bool) error {
|
|
|
|
|
|
+func merge(dst, src interface{}, opts ...func(*Config)) error {
|
|
var (
|
|
var (
|
|
vDst, vSrc reflect.Value
|
|
vDst, vSrc reflect.Value
|
|
err error
|
|
err error
|
|
)
|
|
)
|
|
|
|
+
|
|
|
|
+ config := &Config{}
|
|
|
|
+
|
|
|
|
+ for _, opt := range opts {
|
|
|
|
+ opt(config)
|
|
|
|
+ }
|
|
|
|
+
|
|
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
|
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
if vDst.Type() != vSrc.Type() {
|
|
if vDst.Type() != vSrc.Type() {
|
|
return ErrDifferentArgumentsTypes
|
|
return ErrDifferentArgumentsTypes
|
|
}
|
|
}
|
|
- return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
|
|
|
|
|
|
+ return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
|
}
|
|
}
|