From 4f752267f41ad699070ca81ce5bedc44ca51488e Mon Sep 17 00:00:00 2001 From: thxCode Date: Thu, 14 Oct 2021 14:29:50 +0800 Subject: [PATCH] feat: deduplicate item at slice appending --- merge.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++-- mergo_test.go | 4 ++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/merge.go b/merge.go index 8c2a8fc..3721166 100644 --- a/merge.go +++ b/merge.go @@ -40,6 +40,7 @@ func isExportedComponent(field *reflect.StructField) bool { type Config struct { Overwrite bool AppendSlice bool + deduplicateSliceAppending bool TypeCheck bool Transformers Transformers overwriteWithEmptyValue bool @@ -171,7 +172,31 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co if srcSlice.Type() != dstSlice.Type() { return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) } - dstSlice = reflect.AppendSlice(dstSlice, srcSlice) + if !config.deduplicateSliceAppending { + dstSlice = reflect.AppendSlice(dstSlice, srcSlice) + } else { + for i := 0; i < srcSlice.Len(); i++ { + srcElement := srcSlice.Index(i) + if srcElement.CanInterface() { + srcElement = reflect.ValueOf(srcElement.Interface()) + } + var found bool + for j := 0; j < dstSlice.Len(); j++ { + dstElement := dstSlice.Index(j) + if dstElement.CanInterface() { + dstElement = reflect.ValueOf(dstElement.Interface()) + } + found = reflect.DeepEqual(srcElement.Interface(), dstElement.Interface()) + if found { + break + } + } + if found { + continue + } + dstSlice = reflect.Append(dstSlice, srcSlice.Index(i)) + } + } } else if sliceDeepCopy { i := 0 for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ { @@ -215,7 +240,31 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co if src.Type() != dst.Type() { return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type()) } - dst.Set(reflect.AppendSlice(dst, src)) + if !config.deduplicateSliceAppending { + dst.Set(reflect.AppendSlice(dst, src)) + } else { + for i := 0; i < src.Len(); i++ { + srcElement := src.Index(i) + if srcElement.CanInterface() { + srcElement = reflect.ValueOf(srcElement.Interface()) + } + var found bool + for j := 0; j < dst.Len(); j++ { + dstElement := dst.Index(j) + if dstElement.CanInterface() { + dstElement = reflect.ValueOf(dstElement.Interface()) + } + found = reflect.DeepEqual(srcElement.Interface(), dstElement.Interface()) + if found { + break + } + } + if found { + continue + } + dst.Set(reflect.Append(dst, src.Index(i))) + } + } } else if sliceDeepCopy { for i := 0; i < src.Len() && i < dst.Len(); i++ { srcElement := src.Index(i) @@ -331,6 +380,13 @@ func WithAppendSlice(config *Config) { config.AppendSlice = true } +// WithAppendSliceNonRepeated will make merge append slices instead of overwriting it, +// and deduplicate the slices. +func WithAppendSliceNonRepeated(config *Config) { + config.AppendSlice = true + config.deduplicateSliceAppending = true +} + // WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride). func WithTypeCheck(config *Config) { config.TypeCheck = true diff --git a/mergo_test.go b/mergo_test.go index d69714c..5448ca5 100644 --- a/mergo_test.go +++ b/mergo_test.go @@ -301,6 +301,10 @@ func TestSlice(t *testing.T) { testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice, mergo.WithOverride) testSlice(t, []int{1}, []int{}, []int{1}, mergo.WithAppendSlice) testSlice(t, []int{1}, nil, []int{1}, mergo.WithAppendSlice) + testSlice(t, nil, []int{1}, []int{1}, mergo.WithAppendSliceNonRepeated) + testSlice(t, []int{1}, []int{1}, []int{1}, mergo.WithAppendSliceNonRepeated) + testSlice(t, []int{1, 3}, []int{1, 2}, []int{1, 3, 1, 2}, mergo.WithAppendSlice) + testSlice(t, []int{1, 3}, []int{1, 2}, []int{1, 3, 2}, mergo.WithAppendSliceNonRepeated) } func TestEmptyMaps(t *testing.T) {