Merge tag 'upstream/0.3.5'
Upstream version 0.3.5
Dmitry Smirnov
5 years ago
26 | 26 | |
27 | 27 | ### Latest release |
28 | 28 | |
29 | [Release v0.3.3](https://github.com/imdario/mergo/releases/tag/v0.3.3). | |
29 | [Release v0.3.4](https://github.com/imdario/mergo/releases/tag/v0.3.4). | |
30 | 30 | |
31 | 31 | ### Important note |
32 | 32 | |
33 | Please keep in mind that in [v0.3.2](//github.com/imdario/mergo/releases/tag/v0.3.2) Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). An optional/variadic argument has been added, so it won't break existing code. | |
33 | Please keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2) Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). An optional/variadic argument has been added, so it won't break existing code. | |
34 | 34 | |
35 | 35 | If you were using Mergo **before** April 6th 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause (I hope it won't!) in existing projects after the change (release 0.2.0). |
36 | 36 |
19 | 19 | if err := Merge(&p1, p2); err != nil { |
20 | 20 | t.Fatalf("Error during the merge: %v", err) |
21 | 21 | } |
22 | if len(p1.PublicStrings) != 3 { | |
23 | t.Error("5 elements should be in 'PublicStrings' field") | |
24 | } | |
25 | if len(p1.privateStrings) != 2 { | |
26 | t.Error("2 elements should be in 'privateStrings' field") | |
27 | } | |
28 | } | |
29 | ||
30 | func TestPrivateSliceWithAppendSlice(t *testing.T) { | |
31 | p1 := PrivateSliceTest66{ | |
32 | PublicStrings: []string{"one", "two", "three"}, | |
33 | privateStrings: []string{"four", "five"}, | |
34 | } | |
35 | p2 := PrivateSliceTest66{ | |
36 | PublicStrings: []string{"six", "seven"}, | |
37 | } | |
38 | if err := Merge(&p1, p2, WithAppendSlice); err != nil { | |
39 | t.Fatalf("Error during the merge: %v", err) | |
40 | } | |
22 | 41 | if len(p1.PublicStrings) != 5 { |
23 | 42 | t.Error("5 elements should be in 'PublicStrings' field") |
24 | 43 | } |
25 | 25 | |
26 | 26 | type Config struct { |
27 | 27 | Overwrite bool |
28 | AppendSlice bool | |
28 | 29 | Transformers Transformers |
29 | 30 | } |
30 | 31 | |
101 | 102 | case reflect.Ptr: |
102 | 103 | fallthrough |
103 | 104 | case reflect.Map: |
104 | if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { | |
105 | srcMapElm := srcElement | |
106 | dstMapElm := dstElement | |
107 | if srcMapElm.CanInterface() { | |
108 | srcMapElm = reflect.ValueOf(srcMapElm.Interface()) | |
109 | if dstMapElm.IsValid() { | |
110 | dstMapElm = reflect.ValueOf(dstMapElm.Interface()) | |
111 | } | |
112 | } | |
113 | if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil { | |
105 | 114 | return |
106 | 115 | } |
107 | 116 | case reflect.Slice: |
114 | 123 | dstSlice = reflect.ValueOf(dstElement.Interface()) |
115 | 124 | } |
116 | 125 | |
117 | dstSlice = reflect.AppendSlice(dstSlice, srcSlice) | |
126 | if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice { | |
127 | dstSlice = srcSlice | |
128 | } else if config.AppendSlice { | |
129 | dstSlice = reflect.AppendSlice(dstSlice, srcSlice) | |
130 | } | |
118 | 131 | dst.SetMapIndex(key, dstSlice) |
119 | 132 | } |
120 | 133 | } |
122 | 135 | continue |
123 | 136 | } |
124 | 137 | |
125 | if srcElement.IsValid() && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) { | |
138 | if srcElement.IsValid() && (overwrite || (!dstElement.IsValid() || isEmptyValue(dstElement))) { | |
126 | 139 | if dst.IsNil() { |
127 | 140 | dst.Set(reflect.MakeMap(dst.Type())) |
128 | 141 | } |
133 | 146 | if !dst.CanSet() { |
134 | 147 | break |
135 | 148 | } |
136 | if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) { | |
149 | if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice { | |
137 | 150 | dst.Set(src) |
138 | } else { | |
151 | } else if config.AppendSlice { | |
139 | 152 | dst.Set(reflect.AppendSlice(dst, src)) |
140 | 153 | } |
141 | 154 | case reflect.Ptr: |
204 | 217 | config.Overwrite = true |
205 | 218 | } |
206 | 219 | |
220 | // WithAppendSlice will make merge append slices instead of overwriting it | |
221 | func WithAppendSlice(config *Config) { | |
222 | config.AppendSlice = true | |
223 | } | |
224 | ||
207 | 225 | func merge(dst, src interface{}, opts ...func(*Config)) error { |
208 | 226 | var ( |
209 | 227 | vDst, vSrc reflect.Value |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | var testDataS = []struct { | |
7 | S1 Student | |
8 | S2 Student | |
9 | ExpectedSlice []string | |
10 | }{ | |
11 | {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"1", "a", "B"}}, | |
12 | {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, | |
13 | {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, | |
14 | {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, | |
15 | } | |
16 | ||
17 | func TestMergeSliceWithOverrideWithAppendSlice(t *testing.T) { | |
18 | for _, data := range testDataS { | |
19 | err := Merge(&data.S2, data.S1, WithOverride, WithAppendSlice) | |
20 | if err != nil { | |
21 | t.Errorf("Error while merging %s", err) | |
22 | } | |
23 | if len(data.S2.Books) != len(data.ExpectedSlice) { | |
24 | t.Fatalf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) | |
25 | } | |
26 | for i, val := range data.S2.Books { | |
27 | if val != data.ExpectedSlice[i] { | |
28 | t.Fatalf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) | |
29 | } | |
30 | } | |
31 | } | |
32 | } |
44 | 44 | return v.Uint() == 0 |
45 | 45 | case reflect.Float32, reflect.Float64: |
46 | 46 | return v.Float() == 0 |
47 | case reflect.Interface, reflect.Ptr, reflect.Func: | |
47 | case reflect.Interface, reflect.Ptr: | |
48 | if v.IsNil() { | |
49 | return true | |
50 | } | |
51 | return isEmptyValue(v.Elem()) | |
52 | case reflect.Func: | |
48 | 53 | return v.IsNil() |
49 | 54 | case reflect.Invalid: |
50 | 55 | return true |
5 | 5 | package mergo |
6 | 6 | |
7 | 7 | import ( |
8 | "gopkg.in/yaml.v2" | |
9 | 8 | "io/ioutil" |
10 | 9 | "reflect" |
11 | 10 | "testing" |
12 | 11 | "time" |
12 | ||
13 | "gopkg.in/yaml.v2" | |
13 | 14 | ) |
14 | 15 | |
15 | 16 | type simpleTest struct { |
224 | 225 | } |
225 | 226 | } |
226 | 227 | |
227 | func testSlice(t *testing.T, a []int, b []int) { | |
228 | func testSlice(t *testing.T, a []int, b []int, e []int, opts ...func(*Config)) { | |
229 | t.Helper() | |
228 | 230 | bc := b |
229 | e := append(a, b...) | |
230 | 231 | |
231 | 232 | sa := sliceTest{a} |
232 | 233 | sb := sliceTest{b} |
233 | if err := Merge(&sa, sb); err != nil { | |
234 | if err := Merge(&sa, sb, opts...); err != nil { | |
234 | 235 | t.FailNow() |
235 | 236 | } |
236 | 237 | if !reflect.DeepEqual(sb.S, bc) { |
242 | 243 | |
243 | 244 | ma := map[string][]int{"S": a} |
244 | 245 | mb := map[string][]int{"S": b} |
245 | if err := Merge(&ma, mb); err != nil { | |
246 | if err := Merge(&ma, mb, opts...); err != nil { | |
246 | 247 | t.FailNow() |
247 | 248 | } |
248 | 249 | if !reflect.DeepEqual(mb["S"], bc) { |
249 | t.Fatalf("Source slice was modified %d != %d", mb["S"], bc) | |
250 | t.Fatalf("map value: Source slice was modified %d != %d", mb["S"], bc) | |
250 | 251 | } |
251 | 252 | if !reflect.DeepEqual(ma["S"], e) { |
252 | t.Fatalf("b not merged in a proper way %d != %d", ma["S"], e) | |
253 | t.Fatalf("map value: b not merged in a proper way %d != %d", ma["S"], e) | |
253 | 254 | } |
254 | 255 | |
255 | 256 | if a == nil { |
260 | 261 | t.FailNow() |
261 | 262 | } |
262 | 263 | if !reflect.DeepEqual(mb["S"], bc) { |
263 | t.Fatalf("Source slice was modified %d != %d", mb["S"], bc) | |
264 | t.Fatalf("missing dst key: Source slice was modified %d != %d", mb["S"], bc) | |
264 | 265 | } |
265 | 266 | if !reflect.DeepEqual(ma["S"], e) { |
266 | t.Fatalf("b not merged in a proper way %d != %d", ma["S"], e) | |
267 | t.Fatalf("missing dst key: b not merged in a proper way %d != %d", ma["S"], e) | |
267 | 268 | } |
268 | 269 | } |
269 | 270 | |
275 | 276 | t.FailNow() |
276 | 277 | } |
277 | 278 | if !reflect.DeepEqual(mb["S"], bc) { |
278 | t.Fatalf("Source slice was modified %d != %d", mb["S"], bc) | |
279 | t.Fatalf("missing src key: Source slice was modified %d != %d", mb["S"], bc) | |
279 | 280 | } |
280 | 281 | if !reflect.DeepEqual(ma["S"], e) { |
281 | t.Fatalf("b not merged in a proper way %d != %d", ma["S"], e) | |
282 | t.Fatalf("missing src key: b not merged in a proper way %d != %d", ma["S"], e) | |
282 | 283 | } |
283 | 284 | } |
284 | 285 | } |
285 | 286 | |
286 | 287 | func TestSlice(t *testing.T) { |
287 | testSlice(t, nil, []int{1, 2, 3}) | |
288 | testSlice(t, []int{}, []int{1, 2, 3}) | |
289 | testSlice(t, []int{1}, []int{2, 3}) | |
290 | testSlice(t, []int{1}, []int{}) | |
291 | testSlice(t, []int{1}, nil) | |
288 | testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3}) | |
289 | testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3}) | |
290 | testSlice(t, []int{1}, []int{2, 3}, []int{1}) | |
291 | testSlice(t, []int{1}, []int{}, []int{1}) | |
292 | testSlice(t, []int{1}, nil, []int{1}) | |
293 | testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3}, WithAppendSlice) | |
294 | testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3}, WithAppendSlice) | |
295 | testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, WithAppendSlice) | |
296 | testSlice(t, []int{1}, []int{}, []int{1}, WithAppendSlice) | |
297 | testSlice(t, []int{1}, nil, []int{1}, WithAppendSlice) | |
292 | 298 | } |
293 | 299 | |
294 | 300 | func TestEmptyMaps(t *testing.T) { |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | type mapInterface map[string]interface{} | |
7 | ||
8 | func TestMergeMapsEmptyString(t *testing.T) { | |
9 | a := mapInterface{"s": ""} | |
10 | b := mapInterface{"s": "foo"} | |
11 | if err := Merge(&a, b); err != nil { | |
12 | t.Fatal(err) | |
13 | } | |
14 | if a["s"] != "foo" { | |
15 | t.Fatalf("b not merged in properly: a.s.Value(%s) != expected(%s)", a["s"], "foo") | |
16 | } | |
17 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | func TestMapInterfaceWithMultipleLayer(t *testing.T) { | |
7 | m1 := map[string]interface{}{ | |
8 | "k1": map[string]interface{}{ | |
9 | "k1.1": "v1", | |
10 | }, | |
11 | } | |
12 | ||
13 | m2 := map[string]interface{}{ | |
14 | "k1": map[string]interface{}{ | |
15 | "k1.1": "v2", | |
16 | "k1.2": "v3", | |
17 | }, | |
18 | } | |
19 | ||
20 | if err := Map(&m1, m2, WithOverride); err != nil { | |
21 | t.Fatalf("Error merging: %v", err) | |
22 | } | |
23 | ||
24 | // Check overwrite of sub map works | |
25 | expected := "v2" | |
26 | actual := m1["k1"].(map[string]interface{})["k1.1"].(string) | |
27 | if actual != expected { | |
28 | t.Fatalf("Expected %v but got %v", | |
29 | expected, | |
30 | actual) | |
31 | } | |
32 | ||
33 | // Check new key is merged | |
34 | expected = "v3" | |
35 | actual = m1["k1"].(map[string]interface{})["k1.2"].(string) | |
36 | if actual != expected { | |
37 | t.Fatalf("Expected %v but got %v", | |
38 | expected, | |
39 | actual) | |
40 | } | |
41 | } |