Imported Upstream version 0.2.2
Tim Potter
7 years ago
10 | 10 | It is ready for production use. It works fine after extensive use in the wild. |
11 | 11 | |
12 | 12 | [![Build Status][1]][2] |
13 | [](https://godoc.org/github.com/imdario/mergo) | |
13 | [![GoDoc][3]][4] | |
14 | [![GoCard][5]][6] | |
14 | 15 | |
15 | 16 | [1]: https://travis-ci.org/imdario/mergo.png |
16 | 17 | [2]: https://travis-ci.org/imdario/mergo |
18 | [3]: https://godoc.org/github.com/imdario/mergo?status.svg | |
19 | [4]: https://godoc.org/github.com/imdario/mergo | |
20 | [5]: https://goreportcard.com/badge/imdario/mergo | |
21 | [6]: https://goreportcard.com/report/github.com/imdario/mergo | |
17 | 22 | |
18 | 23 | ### Important note |
19 | 24 | |
20 | Mergo is intended to merge **only** zero value fields on destination. Since April 6th it works like this. Before it didn't work properly, causing some random overwrites. After some issues and PRs I found it didn't merge as I designed it. Thanks to [imdario/mergo#8](https://github.com/imdario/mergo/pull/8) overwriting functions were added and the wrong behavior was clearly detected. | |
25 | Mergo is intended to assign **only** zero value fields on destination with source value. Since April 6th it works like this. Before it didn't work properly, causing some random overwrites. After some issues and PRs I found it didn't merge as I designed it. Thanks to [imdario/mergo#8](https://github.com/imdario/mergo/pull/8) overwriting functions were added and the wrong behavior was clearly detected. | |
21 | 26 | |
22 | 27 | 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). |
23 | 28 | |
24 | 29 | ### Mergo in the wild |
25 | 30 | |
31 | - [docker/docker](https://github.com/docker/docker/) | |
26 | 32 | - [GoogleCloudPlatform/kubernetes](https://github.com/GoogleCloudPlatform/kubernetes) |
33 | - [imdario/zas](https://github.com/imdario/zas) | |
27 | 34 | - [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy) |
28 | 35 | - [EagerIO/Stout](https://github.com/EagerIO/Stout) |
29 | 36 | - [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api) |
32 | 39 | - [casualjim/exeggutor](https://github.com/casualjim/exeggutor) |
33 | 40 | - [divshot/gitling](https://github.com/divshot/gitling) |
34 | 41 | - [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl) |
42 | - [andrerocker/deploy42](https://github.com/andrerocker/deploy42) | |
43 | - [elwinar/rambler](https://github.com/elwinar/rambler) | |
44 | - [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman) | |
45 | - [jfbus/impressionist](https://github.com/jfbus/impressionist) | |
46 | - [Jmeyering/zealot](https://github.com/Jmeyering/zealot) | |
47 | - [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host) | |
48 | - [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go) | |
49 | - [thoas/picfit](https://github.com/thoas/picfit) | |
50 | - [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server) | |
51 | - [jnuthong/item_search](https://github.com/jnuthong/item_search) | |
35 | 52 | |
36 | 53 | ## Installation |
37 | 54 | |
89 | 106 | |
90 | 107 | fmt.Println(dest) |
91 | 108 | // Will print |
92 | // {one 2} | |
109 | // {two 2} | |
93 | 110 | } |
94 | 111 | ``` |
95 | 112 |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | var ( | |
8 | request = `{"timestamp":null, "name": "foo"}` | |
9 | maprequest = map[string]interface{}{ | |
10 | "timestamp": nil, | |
11 | "name": "foo", | |
12 | "newStuff": "foo", | |
13 | } | |
14 | ) | |
15 | ||
16 | func TestIssue17MergeWithOverwrite(t *testing.T) { | |
17 | var something map[string]interface{} | |
18 | if err := json.Unmarshal([]byte(request), &something); err != nil { | |
19 | t.Errorf("Error while Unmarshalling maprequest %s", err) | |
20 | } | |
21 | if err := MergeWithOverwrite(&something, maprequest); err != nil { | |
22 | t.Errorf("Error while merging %s", err) | |
23 | } | |
24 | } |
120 | 120 | return _map(dst, src, false) |
121 | 121 | } |
122 | 122 | |
123 | // MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overriden by | |
124 | // non-empty src attribute values. | |
123 | 125 | func MapWithOverwrite(dst, src interface{}) error { |
124 | 126 | return _map(dst, src, true) |
125 | 127 | } |
45 | 45 | continue |
46 | 46 | } |
47 | 47 | dstElement := dst.MapIndex(key) |
48 | switch reflect.TypeOf(srcElement.Interface()).Kind() { | |
49 | case reflect.Struct: | |
48 | switch srcElement.Kind() { | |
49 | case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice: | |
50 | if srcElement.IsNil() { | |
51 | continue | |
52 | } | |
50 | 53 | fallthrough |
51 | case reflect.Map: | |
52 | if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil { | |
53 | return | |
54 | default: | |
55 | if !srcElement.CanInterface() { | |
56 | continue | |
57 | } | |
58 | switch reflect.TypeOf(srcElement.Interface()).Kind() { | |
59 | case reflect.Struct: | |
60 | fallthrough | |
61 | case reflect.Ptr: | |
62 | fallthrough | |
63 | case reflect.Map: | |
64 | if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil { | |
65 | return | |
66 | } | |
54 | 67 | } |
55 | 68 | } |
56 | 69 | if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) { |
70 | if dst.IsNil() { | |
71 | dst.Set(reflect.MakeMap(dst.Type())) | |
72 | } | |
57 | 73 | dst.SetMapIndex(key, srcElement) |
58 | 74 | } |
59 | 75 | } |
77 | 93 | return |
78 | 94 | } |
79 | 95 | |
80 | // Merge sets fields' values in dst from src if they have a zero | |
81 | // value of their type. | |
82 | // dst and src must be valid same-type structs and dst must be | |
83 | // a pointer to struct. | |
84 | // It won't merge unexported (private) fields and will do recursively | |
85 | // any exported field. | |
96 | // Merge will fill any empty for value type attributes on the dst struct using corresponding | |
97 | // src attributes if they themselves are not empty. dst and src must be valid same-type structs | |
98 | // and dst must be a pointer to struct. | |
99 | // It won't merge unexported (private) fields and will do recursively any exported field. | |
86 | 100 | func Merge(dst, src interface{}) error { |
87 | 101 | return merge(dst, src, false) |
88 | 102 | } |
89 | 103 | |
104 | // MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by | |
105 | // non-empty src attribute values. | |
90 | 106 | func MergeWithOverwrite(dst, src interface{}) error { |
91 | 107 | return merge(dst, src, true) |
92 | 108 | } |
8 | 8 | "io/ioutil" |
9 | 9 | "reflect" |
10 | 10 | "testing" |
11 | "time" | |
11 | 12 | |
12 | 13 | "gopkg.in/yaml.v1" |
13 | 14 | ) |
19 | 20 | type complexTest struct { |
20 | 21 | St simpleTest |
21 | 22 | sz int |
22 | Id string | |
23 | ID string | |
23 | 24 | } |
24 | 25 | |
25 | 26 | type moreComplextText struct { |
101 | 102 | |
102 | 103 | func TestComplexStruct(t *testing.T) { |
103 | 104 | a := complexTest{} |
104 | a.Id = "athing" | |
105 | a.ID = "athing" | |
105 | 106 | b := complexTest{simpleTest{42}, 1, "bthing"} |
106 | 107 | if err := Merge(&a, b); err != nil { |
107 | 108 | t.FailNow() |
112 | 113 | if a.sz == 1 { |
113 | 114 | t.Fatalf("a's private field sz not preserved from merge: a.sz(%d) == b.sz(%d)", a.sz, b.sz) |
114 | 115 | } |
115 | if a.Id == b.Id { | |
116 | t.Fatalf("a's field Id merged unexpectedly: a.Id(%s) == b.Id(%s)", a.Id, b.Id) | |
116 | if a.ID == b.ID { | |
117 | t.Fatalf("a's field ID merged unexpectedly: a.ID(%s) == b.ID(%s)", a.ID, b.ID) | |
117 | 118 | } |
118 | 119 | } |
119 | 120 | |
244 | 245 | |
245 | 246 | func TestMapsWithOverwrite(t *testing.T) { |
246 | 247 | m := map[string]simpleTest{ |
247 | "a": simpleTest{}, // overwritten by 16 | |
248 | "b": simpleTest{42}, // not overwritten by empty value | |
249 | "c": simpleTest{13}, // overwritten by 12 | |
250 | "d": simpleTest{61}, | |
248 | "a": {}, // overwritten by 16 | |
249 | "b": {42}, // not overwritten by empty value | |
250 | "c": {13}, // overwritten by 12 | |
251 | "d": {61}, | |
251 | 252 | } |
252 | 253 | n := map[string]simpleTest{ |
253 | "a": simpleTest{16}, | |
254 | "b": simpleTest{}, | |
255 | "c": simpleTest{12}, | |
256 | "e": simpleTest{14}, | |
254 | "a": {16}, | |
255 | "b": {}, | |
256 | "c": {12}, | |
257 | "e": {14}, | |
257 | 258 | } |
258 | 259 | expect := map[string]simpleTest{ |
259 | "a": simpleTest{16}, | |
260 | "b": simpleTest{}, | |
261 | "c": simpleTest{12}, | |
262 | "d": simpleTest{61}, | |
263 | "e": simpleTest{14}, | |
260 | "a": {16}, | |
261 | "b": {}, | |
262 | "c": {12}, | |
263 | "d": {61}, | |
264 | "e": {14}, | |
264 | 265 | } |
265 | 266 | |
266 | 267 | if err := MergeWithOverwrite(&m, n); err != nil { |
274 | 275 | |
275 | 276 | func TestMaps(t *testing.T) { |
276 | 277 | m := map[string]simpleTest{ |
277 | "a": simpleTest{}, | |
278 | "b": simpleTest{42}, | |
279 | "c": simpleTest{13}, | |
280 | "d": simpleTest{61}, | |
278 | "a": {}, | |
279 | "b": {42}, | |
280 | "c": {13}, | |
281 | "d": {61}, | |
281 | 282 | } |
282 | 283 | n := map[string]simpleTest{ |
283 | "a": simpleTest{16}, | |
284 | "b": simpleTest{}, | |
285 | "c": simpleTest{12}, | |
286 | "e": simpleTest{14}, | |
284 | "a": {16}, | |
285 | "b": {}, | |
286 | "c": {12}, | |
287 | "e": {14}, | |
287 | 288 | } |
288 | 289 | expect := map[string]simpleTest{ |
289 | "a": simpleTest{0}, | |
290 | "b": simpleTest{42}, | |
291 | "c": simpleTest{13}, | |
292 | "d": simpleTest{61}, | |
293 | "e": simpleTest{14}, | |
290 | "a": {0}, | |
291 | "b": {42}, | |
292 | "c": {13}, | |
293 | "d": {61}, | |
294 | "e": {14}, | |
294 | 295 | } |
295 | 296 | |
296 | 297 | if err := Merge(&m, n); err != nil { |
340 | 341 | |
341 | 342 | func TestMap(t *testing.T) { |
342 | 343 | a := complexTest{} |
343 | a.Id = "athing" | |
344 | a.ID = "athing" | |
344 | 345 | c := moreComplextText{a, simpleTest{}, simpleTest{}} |
345 | 346 | b := map[string]interface{}{ |
346 | 347 | "ct": map[string]interface{}{ |
373 | 374 | if c.Ct.sz == 1 { |
374 | 375 | t.Fatalf("a's private field sz not preserved from merge: c.Ct.sz(%d) == b.Ct.sz(%d)", c.Ct.sz, m["sz"]) |
375 | 376 | } |
376 | if c.Ct.Id == m["id"] { | |
377 | t.Fatalf("a's field Id merged unexpectedly: c.Ct.Id(%s) == b.Ct.Id(%s)", c.Ct.Id, m["id"]) | |
377 | if c.Ct.ID == m["id"] { | |
378 | t.Fatalf("a's field ID merged unexpectedly: c.Ct.ID(%s) == b.Ct.ID(%s)", c.Ct.ID, m["id"]) | |
378 | 379 | } |
379 | 380 | } |
380 | 381 | |
432 | 433 | } |
433 | 434 | } |
434 | 435 | |
436 | type structWithTimePointer struct { | |
437 | Birth *time.Time | |
438 | } | |
439 | ||
440 | func TestTime(t *testing.T) { | |
441 | now := time.Now() | |
442 | dataStruct := structWithTimePointer{ | |
443 | Birth: &now, | |
444 | } | |
445 | dataMap := map[string]interface{}{ | |
446 | "Birth": &now, | |
447 | } | |
448 | b := structWithTimePointer{} | |
449 | if err := Merge(&b, dataStruct); err != nil { | |
450 | t.FailNow() | |
451 | } | |
452 | if b.Birth.IsZero() { | |
453 | t.Fatalf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth) | |
454 | } | |
455 | if b.Birth != dataStruct.Birth { | |
456 | t.Fatalf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth) | |
457 | } | |
458 | b = structWithTimePointer{} | |
459 | if err := Map(&b, dataMap); err != nil { | |
460 | t.FailNow() | |
461 | } | |
462 | if b.Birth.IsZero() { | |
463 | t.Fatalf("time.Time not merged in properly: b.Birth(%v) != dataMap['Birth'](%v)", b.Birth, dataMap["Birth"]) | |
464 | } | |
465 | } | |
466 | ||
467 | type simpleNested struct { | |
468 | A int | |
469 | } | |
470 | ||
471 | type structWithNestedPtrValueMap struct { | |
472 | NestedPtrValue map[string]*simpleNested | |
473 | } | |
474 | ||
475 | func TestNestedPtrValueInMap(t *testing.T) { | |
476 | src := &structWithNestedPtrValueMap{ | |
477 | NestedPtrValue: map[string]*simpleNested{ | |
478 | "x": { | |
479 | A: 1, | |
480 | }, | |
481 | }, | |
482 | } | |
483 | dst := &structWithNestedPtrValueMap{ | |
484 | NestedPtrValue: map[string]*simpleNested{ | |
485 | "x": {}, | |
486 | }, | |
487 | } | |
488 | if err := Map(dst, src); err != nil { | |
489 | t.FailNow() | |
490 | } | |
491 | if dst.NestedPtrValue["x"].A == 0 { | |
492 | t.Fatalf("Nested Ptr value not merged in properly: dst.NestedPtrValue[\"x\"].A(%v) != src.NestedPtrValue[\"x\"].A(%v)", dst.NestedPtrValue["x"].A, src.NestedPtrValue["x"].A) | |
493 | } | |
494 | } | |
495 | ||
435 | 496 | func loadYAML(path string) (m map[string]interface{}) { |
436 | 497 | m = make(map[string]interface{}) |
437 | 498 | raw, _ := ioutil.ReadFile(path) |
438 | 499 | _ = yaml.Unmarshal(raw, &m) |
439 | 500 | return |
440 | 501 | } |
502 | ||
503 | type structWithMap struct { | |
504 | m map[string]structWithUnexportedProperty | |
505 | } | |
506 | ||
507 | type structWithUnexportedProperty struct { | |
508 | s string | |
509 | } | |
510 | ||
511 | func TestUnexportedProperty(t *testing.T) { | |
512 | a := structWithMap{map[string]structWithUnexportedProperty{ | |
513 | "key": structWithUnexportedProperty{"hello"}, | |
514 | }} | |
515 | b := structWithMap{map[string]structWithUnexportedProperty{ | |
516 | "key": structWithUnexportedProperty{"hi"}, | |
517 | }} | |
518 | defer func() { | |
519 | if r := recover(); r != nil { | |
520 | t.Errorf("Should not have panicked") | |
521 | } | |
522 | }() | |
523 | Merge(&a, b) | |
524 | } |