Codebase list golang-github-imdario-mergo / d415265
Imported Upstream version 0.2.2 Tim Potter 7 years ago
5 changed file(s) with 191 addition(s) and 47 deletion(s). Raw diff Collapse all Expand all
1010 It is ready for production use. It works fine after extensive use in the wild.
1111
1212 [![Build Status][1]][2]
13 [![GoDoc](https://godoc.org/github.com/imdario/mergo?status.svg)](https://godoc.org/github.com/imdario/mergo)
13 [![GoDoc][3]][4]
14 [![GoCard][5]][6]
1415
1516 [1]: https://travis-ci.org/imdario/mergo.png
1617 [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
1722
1823 ### Important note
1924
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.
2126
2227 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).
2328
2429 ### Mergo in the wild
2530
31 - [docker/docker](https://github.com/docker/docker/)
2632 - [GoogleCloudPlatform/kubernetes](https://github.com/GoogleCloudPlatform/kubernetes)
33 - [imdario/zas](https://github.com/imdario/zas)
2734 - [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
2835 - [EagerIO/Stout](https://github.com/EagerIO/Stout)
2936 - [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
3239 - [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
3340 - [divshot/gitling](https://github.com/divshot/gitling)
3441 - [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)
3552
3653 ## Installation
3754
89106
90107 fmt.Println(dest)
91108 // Will print
92 // {one 2}
109 // {two 2}
93110 }
94111 ```
95112
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 }
120120 return _map(dst, src, false)
121121 }
122122
123 // MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overriden by
124 // non-empty src attribute values.
123125 func MapWithOverwrite(dst, src interface{}) error {
124126 return _map(dst, src, true)
125127 }
4545 continue
4646 }
4747 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 }
5053 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 }
5467 }
5568 }
5669 if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) {
70 if dst.IsNil() {
71 dst.Set(reflect.MakeMap(dst.Type()))
72 }
5773 dst.SetMapIndex(key, srcElement)
5874 }
5975 }
7793 return
7894 }
7995
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.
86100 func Merge(dst, src interface{}) error {
87101 return merge(dst, src, false)
88102 }
89103
104 // MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by
105 // non-empty src attribute values.
90106 func MergeWithOverwrite(dst, src interface{}) error {
91107 return merge(dst, src, true)
92108 }
88 "io/ioutil"
99 "reflect"
1010 "testing"
11 "time"
1112
1213 "gopkg.in/yaml.v1"
1314 )
1920 type complexTest struct {
2021 St simpleTest
2122 sz int
22 Id string
23 ID string
2324 }
2425
2526 type moreComplextText struct {
101102
102103 func TestComplexStruct(t *testing.T) {
103104 a := complexTest{}
104 a.Id = "athing"
105 a.ID = "athing"
105106 b := complexTest{simpleTest{42}, 1, "bthing"}
106107 if err := Merge(&a, b); err != nil {
107108 t.FailNow()
112113 if a.sz == 1 {
113114 t.Fatalf("a's private field sz not preserved from merge: a.sz(%d) == b.sz(%d)", a.sz, b.sz)
114115 }
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)
117118 }
118119 }
119120
244245
245246 func TestMapsWithOverwrite(t *testing.T) {
246247 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},
251252 }
252253 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},
257258 }
258259 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},
264265 }
265266
266267 if err := MergeWithOverwrite(&m, n); err != nil {
274275
275276 func TestMaps(t *testing.T) {
276277 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},
281282 }
282283 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},
287288 }
288289 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},
294295 }
295296
296297 if err := Merge(&m, n); err != nil {
340341
341342 func TestMap(t *testing.T) {
342343 a := complexTest{}
343 a.Id = "athing"
344 a.ID = "athing"
344345 c := moreComplextText{a, simpleTest{}, simpleTest{}}
345346 b := map[string]interface{}{
346347 "ct": map[string]interface{}{
373374 if c.Ct.sz == 1 {
374375 t.Fatalf("a's private field sz not preserved from merge: c.Ct.sz(%d) == b.Ct.sz(%d)", c.Ct.sz, m["sz"])
375376 }
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"])
378379 }
379380 }
380381
432433 }
433434 }
434435
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
435496 func loadYAML(path string) (m map[string]interface{}) {
436497 m = make(map[string]interface{})
437498 raw, _ := ioutil.ReadFile(path)
438499 _ = yaml.Unmarshal(raw, &m)
439500 return
440501 }
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 }