diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000..8a0681a --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,12 @@ +version = 1 + +test_patterns = [ + "*_test.go" +] + +[[analyzers]] +name = "go" +enabled = true + + [analyzers.meta] + import_path = "github.com/imdario/mergo" \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 07939d3..2aea4d6 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,5 @@ # These are supported funding model platforms +github: imdario ko_fi: dariocc custom: https://beerpay.io/imdario/mergo diff --git a/.travis.yml b/.travis.yml index dad2972..d324c43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,7 @@ language: go +arch: + - amd64 + - ppc64le install: - go get -t - go get golang.org/x/tools/cmd/cover diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..85f3f37 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "go.lintTool": "golangci-lint", + "go.lintFlags": [ + "--enable-all", + "--disable=gomnd" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 02fc81e..71f97c0 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,54 @@ # Mergo -A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. - -Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche. - -## Status - -It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild). [![GoDoc][3]][4] -[![GoCard][5]][6] +[![GitHub release][5]][6] +[![GoCard][7]][8] [![Build Status][1]][2] -[![Coverage Status][7]][8] -[![Sourcegraph][9]][10] -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield) +[![Coverage Status][9]][10] +[![Sourcegraph][11]][12] +[![FOSSA Status][13]][14] + +[![GoCenter Kudos][15]][16] [1]: https://travis-ci.org/imdario/mergo.png [2]: https://travis-ci.org/imdario/mergo [3]: https://godoc.org/github.com/imdario/mergo?status.svg [4]: https://godoc.org/github.com/imdario/mergo -[5]: https://goreportcard.com/badge/imdario/mergo -[6]: https://goreportcard.com/report/github.com/imdario/mergo -[7]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master -[8]: https://coveralls.io/github/imdario/mergo?branch=master -[9]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg -[10]: https://sourcegraph.com/github.com/imdario/mergo?badge - -### Latest release - -[Release v0.3.7](https://github.com/imdario/mergo/releases/tag/v0.3.7). +[5]: https://img.shields.io/github/release/imdario/mergo.svg +[6]: https://github.com/imdario/mergo/releases +[7]: https://goreportcard.com/badge/imdario/mergo +[8]: https://goreportcard.com/report/github.com/imdario/mergo +[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master +[10]: https://coveralls.io/github/imdario/mergo?branch=master +[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg +[12]: https://sourcegraph.com/github.com/imdario/mergo?badge +[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield +[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield +[15]: https://search.gocenter.io/api/ui/badge/github.com%2Fimdario%2Fmergo +[16]: https://search.gocenter.io/github.com/imdario/mergo + +A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. + +Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). + +Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche. + +## Status + +It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild). ### Important note -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. - -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). +Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules. + +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). I added an optional/variadic argument so that it won't break the existing code. + +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 in existing projects after the change (release 0.2.0). ### Donations -If Mergo is useful to you, consider buying me a coffee, a beer or making a monthly donation so I can keep building great free software. :heart_eyes: +If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes: Buy Me a Coffee at ko-fi.com [![Beerpay](https://beerpay.io/imdario/mergo/badge.svg)](https://beerpay.io/imdario/mergo) @@ -87,8 +97,9 @@ - [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server) - [jnuthong/item_search](https://github.com/jnuthong/item_search) - [bukalapak/snowboard](https://github.com/bukalapak/snowboard) - -## Installation +- [containerssh/containerssh](https://github.com/containerssh/containerssh) + +## Install go get github.com/imdario/mergo @@ -99,7 +110,7 @@ ## Usage -You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are not considered zero values](https://golang.org/ref/spec#The_zero_value) either. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). +You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). ```go if err := mergo.Merge(&dst, src); err != nil { @@ -125,9 +136,7 @@ Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values. -More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo). - -### Nice example +Here is a nice example: ```go package main @@ -175,10 +184,10 @@ "time" ) -type timeTransfomer struct { -} - -func (t timeTransfomer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { +type timeTransformer struct { +} + +func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { if typ == reflect.TypeOf(time.Time{}) { return func(dst, src reflect.Value) error { if dst.CanSet() { @@ -202,7 +211,7 @@ func main() { src := Snapshot{time.Now()} dest := Snapshot{} - mergo.Merge(&dest, src, mergo.WithTransformers(timeTransfomer{})) + mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) fmt.Println(dest) // Will print // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } diff --git a/debian/changelog b/debian/changelog index 6c5d7f7..5d4d4cc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +golang-github-imdario-mergo (0.3.12+git20210420.1.b968a17-1) UNRELEASED; urgency=low + + * New upstream snapshot. + + -- Debian Janitor Mon, 07 Jun 2021 23:23:20 -0000 + golang-github-imdario-mergo (0.3.8-3) unstable; urgency=medium [ Debian Janitor ] diff --git a/doc.go b/doc.go index 6e9aa7b..fcd985f 100644 --- a/doc.go +++ b/doc.go @@ -4,41 +4,140 @@ // license that can be found in the LICENSE file. /* -Package mergo merges same-type structs and maps by setting default values in zero-value fields. +A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. -Mergo won't merge unexported (private) fields but will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). +Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). + +Status + +It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc. + +Important note + +Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules. + +Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code. + +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 in existing projects after the change (release 0.2.0). + +Install + +Do your usual installation procedure: + + go get github.com/imdario/mergo + + // use in your .go code + import ( + "github.com/imdario/mergo" + ) Usage -From my own work-in-progress project: +You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). - type networkConfig struct { - Protocol string - Address string - ServerType string `json: "server_type"` - Port uint16 + if err := mergo.Merge(&dst, src); err != nil { + // ... } - type FssnConfig struct { - Network networkConfig +Also, you can merge overwriting values using the transformer WithOverride. + + if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { + // ... } - var fssnDefault = FssnConfig { - networkConfig { - "tcp", - "127.0.0.1", - "http", - 31560, - }, +Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field. + + if err := mergo.Map(&dst, srcMap); err != nil { + // ... } - // Inside a function [...] +Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values. - if err := mergo.Merge(&config, fssnDefault); err != nil { - log.Fatal(err) +Here is a nice example: + + package main + + import ( + "fmt" + "github.com/imdario/mergo" + ) + + type Foo struct { + A string + B int64 } - // More code [...] + func main() { + src := Foo{ + A: "one", + B: 2, + } + dest := Foo{ + A: "two", + } + mergo.Merge(&dest, src) + fmt.Println(dest) + // Will print + // {two 2} + } + +Transformers + +Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time? + + package main + + import ( + "fmt" + "github.com/imdario/mergo" + "reflect" + "time" + ) + + type timeTransformer struct { + } + + func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { + if typ == reflect.TypeOf(time.Time{}) { + return func(dst, src reflect.Value) error { + if dst.CanSet() { + isZero := dst.MethodByName("IsZero") + result := isZero.Call([]reflect.Value{}) + if result[0].Bool() { + dst.Set(src) + } + } + return nil + } + } + return nil + } + + type Snapshot struct { + Time time.Time + // ... + } + + func main() { + src := Snapshot{time.Now()} + dest := Snapshot{} + mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) + fmt.Println(dest) + // Will print + // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } + } + +Contact me + +If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario + +About + +Written by Dario Castañé: https://da.rio.hn + +License + +BSD 3-Clause license, as Go language. */ package mergo diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3d689d9 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/imdario/mergo + +go 1.13 + +require gopkg.in/yaml.v2 v2.3.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..168980d --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/issue100_test.go b/issue100_test.go new file mode 100644 index 0000000..039123a --- /dev/null +++ b/issue100_test.go @@ -0,0 +1,21 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +type issue100s struct { + Member interface{} +} + +func TestIssue100(t *testing.T) { + m := make(map[string]interface{}) + m["Member"] = "anything" + + st := &issue100s{} + if err := mergo.Map(st, m); err != nil { + t.Error(err) + } +} diff --git a/issue104_test.go b/issue104_test.go new file mode 100644 index 0000000..c07e5a8 --- /dev/null +++ b/issue104_test.go @@ -0,0 +1,48 @@ +package mergo_test + +import ( + "reflect" + "testing" + + "github.com/imdario/mergo" +) + +type Record struct { + Data map[string]interface{} + Mapping map[string]string +} + +func StructToRecord(in interface{}) *Record { + rec := Record{} + rec.Data = make(map[string]interface{}) + rec.Mapping = make(map[string]string) + typ := reflect.TypeOf(in) + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + dbFieldName := field.Tag.Get("db") + if dbFieldName != "" { + rec.Mapping[field.Name] = dbFieldName + } + } + + if err := mergo.Map(&rec.Data, in); err != nil { + panic(err) + } + return &rec +} + +func TestStructToRecord(t *testing.T) { + type A struct { + Name string `json:"name" db:"name"` + CIDR string `json:"cidr" db:"cidr"` + } + type Record struct { + Data map[string]interface{} + Mapping map[string]string + } + a := A{Name: "David", CIDR: "10.0.0.0/8"} + rec := StructToRecord(a) + if len(rec.Mapping) < 2 { + t.Fatalf("struct to record failed, no mapping, struct missing tags?, rec: %+v, a: %+v ", rec, a) + } +} diff --git a/issue121_test.go b/issue121_test.go new file mode 100644 index 0000000..0f36efd --- /dev/null +++ b/issue121_test.go @@ -0,0 +1,35 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +func TestIssue121WithSliceDeepCopy(t *testing.T) { + dst := map[string]interface{}{ + "inter": map[string]interface{}{ + "a": "1", + "b": "2", + }, + } + + src := map[string]interface{}{ + "inter": map[string]interface{}{ + "a": "3", + "c": "4", + }, + } + + if err := mergo.Merge(&dst, src, mergo.WithSliceDeepCopy); err != nil { + t.Errorf("Error during the merge: %v", err) + } + + if dst["inter"].(map[string]interface{})["a"].(string) != "3" { + t.Error("inter.a should equal '3'") + } + + if dst["inter"].(map[string]interface{})["c"].(string) != "4" { + t.Error("inter.c should equal '4'") + } +} diff --git a/issue123_test.go b/issue123_test.go new file mode 100644 index 0000000..ae62d7b --- /dev/null +++ b/issue123_test.go @@ -0,0 +1,47 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +func TestIssue123(t *testing.T) { + src := map[string]interface{}{ + "col1": nil, + "col2": 4, + "col3": nil, + } + dst := map[string]interface{}{ + "col1": 2, + "col2": 3, + "col3": 3, + } + + // Expected behavior + if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { + t.Fatal(err) + } + testCases := []struct { + key string + expected interface{} + }{ + { + "col1", + nil, + }, + { + "col2", + 4, + }, + { + "col3", + nil, + }, + } + for _, tC := range testCases { + if dst[tC.key] != tC.expected { + t.Fatalf("expected %v in dst[%q], got %v", tC.expected, tC.key, dst[tC.key]) + } + } +} diff --git a/issue125_test.go b/issue125_test.go index 69cc894..4e5cb63 100644 --- a/issue125_test.go +++ b/issue125_test.go @@ -1,12 +1,10 @@ -package mergo +package mergo_test import ( "encoding/json" "testing" -) -var ( - data = `{"FirstSlice":[], "SecondSlice": null}` + "github.com/imdario/mergo" ) type settings struct { @@ -15,22 +13,27 @@ } func TestIssue125MergeWithOverwrite(t *testing.T) { + var ( + defaultSettings = settings{ + FirstSlice: []string{}, + SecondSlice: []string{}, + } + something settings + data = `{"FirstSlice":[], "SecondSlice": null}` + ) - defaultSettings := settings{ - FirstSlice: []string{}, - SecondSlice: []string{}, - } - - var something settings if err := json.Unmarshal([]byte(data), &something); err != nil { t.Errorf("Error while Unmarshalling maprequest: %s", err) } - if err := Merge(&something, defaultSettings, WithOverrideEmptySlice); err != nil { + + if err := mergo.Merge(&something, defaultSettings, mergo.WithOverrideEmptySlice); err != nil { t.Errorf("Error while merging: %s", err) } + if something.FirstSlice == nil { t.Error("Invalid merging first slice") } + if something.SecondSlice == nil { t.Error("Invalid merging second slice") } diff --git a/issue129_test.go b/issue129_test.go new file mode 100644 index 0000000..3a1c691 --- /dev/null +++ b/issue129_test.go @@ -0,0 +1,49 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +func TestIssue129Boolean(t *testing.T) { + type Foo struct { + A bool + B bool + } + + src := Foo{ + A: true, + B: false, + } + dst := Foo{ + A: false, + B: true, + } + + // Standard behavior + if err := mergo.Merge(&dst, src); err != nil { + t.Error(err) + } + if dst.A != true { + t.Errorf("expected true, got false") + } + if dst.B != true { + t.Errorf("expected true, got false") + } + + // Expected behavior + dst = Foo{ + A: false, + B: true, + } + if err := mergo.Merge(&dst, src, mergo.WithOverwriteWithEmptyValue); err != nil { + t.Error(err) + } + if dst.A != true { + t.Errorf("expected true, got false") + } + if dst.B != false { + t.Errorf("expected false, got true") + } +} diff --git a/issue131_test.go b/issue131_test.go new file mode 100644 index 0000000..5ca24cf --- /dev/null +++ b/issue131_test.go @@ -0,0 +1,32 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +type foz struct { + A *bool + B string +} + +func TestIssue131MergeWithOverwriteWithEmptyValue(t *testing.T) { + src := foz{ + A: func(v bool) *bool { return &v }(false), + B: "src", + } + dest := foz{ + A: func(v bool) *bool { return &v }(true), + B: "dest", + } + if err := mergo.Merge(&dest, src, mergo.WithOverwriteWithEmptyValue); err != nil { + t.Error(err) + } + if *src.A != *dest.A { + t.Errorf("dest.A not merged in properly: %v != %v", *src.A, *dest.A) + } + if src.B != dest.B { + t.Errorf("dest.B not merged in properly: %v != %v", src.B, dest.B) + } +} diff --git a/issue136_test.go b/issue136_test.go new file mode 100644 index 0000000..4fe81c7 --- /dev/null +++ b/issue136_test.go @@ -0,0 +1,35 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +type embeddedTestA struct { + Name string + Age uint8 +} + +type embeddedTestB struct { + embeddedTestA + Address string +} + +func TestMergeEmbedded(t *testing.T) { + var ( + err error + a = &embeddedTestA{ + "Suwon", 16, + } + b = &embeddedTestB{} + ) + + if err := mergo.Merge(&b.embeddedTestA, *a); err != nil { + t.Error(err) + } + + if b.Name != "Suwon" { + t.Errorf("%v %v", b.Name, err) + } +} diff --git a/issue138_test.go b/issue138_test.go new file mode 100644 index 0000000..70d7b29 --- /dev/null +++ b/issue138_test.go @@ -0,0 +1,42 @@ +package mergo_test + +import ( + "encoding/json" + "testing" + + "github.com/imdario/mergo" +) + +const issue138configuration string = ` +{ + "Port": 80 +} +` + +func TestIssue138(t *testing.T) { + type config struct { + Port uint16 + } + type compatibleConfig struct { + Port float64 + } + + foo := make(map[string]interface{}) + // encoding/json unmarshals numbers as float64 + // https://golang.org/pkg/encoding/json/#Unmarshal + json.Unmarshal([]byte(issue138configuration), &foo) + + err := mergo.Map(&config{}, foo) + if err == nil { + t.Error("expected type mismatch error, got nil") + } else { + if err.Error() != "type mismatch on Port field: found float64, expected uint16" { + t.Errorf("expected type mismatch error, got %q", err) + } + } + + c := compatibleConfig{} + if err := mergo.Map(&c, foo); err != nil { + t.Error(err) + } +} diff --git a/issue143_test.go b/issue143_test.go new file mode 100644 index 0000000..8cca4d8 --- /dev/null +++ b/issue143_test.go @@ -0,0 +1,61 @@ +package mergo_test + +import ( + "fmt" + "testing" + + "github.com/imdario/mergo" +) + +func TestIssue143(t *testing.T) { + testCases := []struct { + options []func(*mergo.Config) + expected func(map[string]interface{}) error + }{ + { + options: []func(*mergo.Config){mergo.WithOverride}, + expected: func(m map[string]interface{}) error { + properties := m["properties"].(map[string]interface{}) + if properties["field1"] != "wrong" { + return fmt.Errorf("expected %q, got %v", "wrong", properties["field1"]) + } + return nil + }, + }, + { + options: []func(*mergo.Config){}, + expected: func(m map[string]interface{}) error { + properties := m["properties"].(map[string]interface{}) + if properties["field1"] == "wrong" { + return fmt.Errorf("expected a map, got %v", "wrong") + } + return nil + }, + }, + } + for _, tC := range testCases { + base := map[string]interface{}{ + "properties": map[string]interface{}{ + "field1": map[string]interface{}{ + "type": "text", + }, + }, + } + + err := mergo.Map( + &base, + map[string]interface{}{ + "properties": map[string]interface{}{ + "field1": "wrong", + }, + }, + tC.options..., + ) + if err != nil { + t.Error(err) + } + if err := tC.expected(base); err != nil { + t.Error(err) + } + } +} diff --git a/issue149_test.go b/issue149_test.go new file mode 100644 index 0000000..b6857c9 --- /dev/null +++ b/issue149_test.go @@ -0,0 +1,39 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +type user struct { + Name string +} + +type token struct { + User *user + Token *string +} + +func TestIssue149(t *testing.T) { + dest := &token{ + User: &user{ + Name: "destination", + }, + Token: nil, + } + tokenValue := "Issue149" + src := &token{ + User: nil, + Token: &tokenValue, + } + if err := mergo.Merge(dest, src, mergo.WithOverwriteWithEmptyValue); err != nil { + t.Error(err) + } + if dest.User != nil { + t.Errorf("expected nil User, got %q", dest.User) + } + if dest.Token == nil { + t.Errorf("expected not nil Token, got %q", *dest.Token) + } +} diff --git a/issue174_test.go b/issue174_test.go new file mode 100644 index 0000000..d06b68b --- /dev/null +++ b/issue174_test.go @@ -0,0 +1,21 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +type structWithBlankField struct { + _ struct{} + A struct{} +} + +func TestIssue174(t *testing.T) { + dst := structWithBlankField{} + src := structWithBlankField{} + + if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { + t.Error(err) + } +} diff --git a/issue17_test.go b/issue17_test.go index f9de805..97bbd7d 100644 --- a/issue17_test.go +++ b/issue17_test.go @@ -1,25 +1,28 @@ -package mergo +package mergo_test import ( "encoding/json" "testing" -) -var ( - request = `{"timestamp":null, "name": "foo"}` - maprequest = map[string]interface{}{ - "timestamp": nil, - "name": "foo", - "newStuff": "foo", - } + "github.com/imdario/mergo" ) func TestIssue17MergeWithOverwrite(t *testing.T) { + var ( + request = `{"timestamp":null, "name": "foo"}` + maprequest = map[string]interface{}{ + "timestamp": nil, + "name": "foo", + "newStuff": "foo", + } + ) + var something map[string]interface{} if err := json.Unmarshal([]byte(request), &something); err != nil { t.Errorf("Error while Unmarshalling maprequest: %s", err) } - if err := MergeWithOverwrite(&something, maprequest); err != nil { + + if err := mergo.MergeWithOverwrite(&something, maprequest); err != nil { t.Errorf("Error while merging: %s", err) } } diff --git a/issue23_test.go b/issue23_test.go index 283f8c6..d91bd79 100644 --- a/issue23_test.go +++ b/issue23_test.go @@ -1,8 +1,10 @@ -package mergo +package mergo_test import ( "testing" "time" + + "github.com/imdario/mergo" ) type document struct { @@ -18,10 +20,12 @@ src := document{ &expected, } - if err := MergeWithOverwrite(&dst, src); err != nil { + + if err := mergo.MergeWithOverwrite(&dst, src); err != nil { t.Errorf("Error while merging %s", err) } + if !dst.Created.Equal(*src.Created) { //--> https://golang.org/pkg/time/#pkg-overview - t.Fatalf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) + t.Errorf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) } } diff --git a/issue33_test.go b/issue33_test.go index ae55ae2..cf92735 100644 --- a/issue33_test.go +++ b/issue33_test.go @@ -1,7 +1,9 @@ -package mergo +package mergo_test import ( "testing" + + "github.com/imdario/mergo" ) type Foo struct { @@ -15,7 +17,8 @@ Str: "b", Bslice: []byte{1, 2}, } - if err := Merge(&dest, toMerge); err != nil { + + if err := mergo.Merge(&dest, toMerge); err != nil { t.Errorf("Error while merging: %s", err) } // Merge doesn't overwrite an attribute if in destination it doesn't have a zero value. @@ -24,9 +27,10 @@ t.Errorf("dest.Str should have not been override as it has a non-zero value: dest.Str(%v) != 'a'", dest.Str) } // If we want to override, we must use MergeWithOverwrite or Merge using WithOverride. - if err := Merge(&dest, toMerge, WithOverride); err != nil { + if err := mergo.Merge(&dest, toMerge, mergo.WithOverride); err != nil { t.Errorf("Error while merging: %s", err) } + if dest.Str != toMerge.Str { t.Errorf("dest.Str should have been override: dest.Str(%v) != toMerge.Str(%v)", dest.Str, toMerge.Str) } diff --git a/issue38_test.go b/issue38_test.go index 286b68c..fdee7ff 100644 --- a/issue38_test.go +++ b/issue38_test.go @@ -1,8 +1,10 @@ -package mergo +package mergo_test import ( "testing" "time" + + "github.com/imdario/mergo" ) type structWithoutTimePointer struct { @@ -18,11 +20,13 @@ src := structWithoutTimePointer{ expected, } - if err := Merge(&dst, src); err != nil { + + if err := mergo.Merge(&dst, src); err != nil { t.Errorf("Error while merging %s", err) } + if dst.Created == src.Created { - t.Fatalf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) + t.Errorf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) } } @@ -33,11 +37,13 @@ src := structWithoutTimePointer{ expected, } - if err := Merge(&dst, src); err != nil { + + if err := mergo.Merge(&dst, src); err != nil { t.Errorf("Error while merging %s", err) } + if dst.Created == src.Created { - t.Fatalf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) + t.Errorf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) } } @@ -50,10 +56,12 @@ src := structWithoutTimePointer{ expected, } - if err := MergeWithOverwrite(&dst, src); err != nil { + + if err := mergo.MergeWithOverwrite(&dst, src); err != nil { t.Errorf("Error while merging %s", err) } + if dst.Created != src.Created { - t.Fatalf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) + t.Errorf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) } } diff --git a/issue50_test.go b/issue50_test.go index 89aa363..2d32579 100644 --- a/issue50_test.go +++ b/issue50_test.go @@ -1,8 +1,10 @@ -package mergo +package mergo_test import ( "testing" "time" + + "github.com/imdario/mergo" ) type testStruct struct { @@ -12,7 +14,8 @@ func TestIssue50Merge(t *testing.T) { to := testStruct{} from := testStruct{} - if err := Merge(&to, from); err != nil { + + if err := mergo.Merge(&to, from); err != nil { t.Fail() } } diff --git a/issue52_test.go b/issue52_test.go index 62cd9fa..af65471 100644 --- a/issue52_test.go +++ b/issue52_test.go @@ -1,9 +1,11 @@ -package mergo +package mergo_test import ( "reflect" "testing" "time" + + "github.com/imdario/mergo" ) type structWithTime struct { @@ -20,12 +22,14 @@ if dst.CanSet() { if t.overwrite { isZero := src.MethodByName("IsZero") + result := isZero.Call([]reflect.Value{}) if !result[0].Bool() { dst.Set(src) } } else { isZero := dst.MethodByName("IsZero") + result := isZero.Call([]reflect.Value{}) if result[0].Bool() { dst.Set(src) @@ -42,11 +46,13 @@ now := time.Now() dst := structWithTime{now} src := structWithTime{} - if err := MergeWithOverwrite(&dst, src); err != nil { + + if err := mergo.MergeWithOverwrite(&dst, src); err != nil { t.FailNow() } + if !dst.Birth.IsZero() { - t.Fatalf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) + t.Errorf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) } } @@ -54,11 +60,13 @@ now := time.Now() dst := structWithTime{now} src := structWithTime{} - if err := MergeWithOverwrite(&dst, src, WithTransformers(timeTransfomer{true})); err != nil { + + if err := mergo.MergeWithOverwrite(&dst, src, mergo.WithTransformers(timeTransfomer{true})); err != nil { t.FailNow() } + if dst.Birth.IsZero() { - t.Fatalf("dst should not have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) + t.Errorf("dst should not have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) } } @@ -66,11 +74,13 @@ now := time.Now() dst := structWithTime{} src := structWithTime{now} - if err := MergeWithOverwrite(&dst, src); err != nil { + + if err := mergo.MergeWithOverwrite(&dst, src); err != nil { t.FailNow() } + if dst.Birth.IsZero() { - t.Fatalf("dst should have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) + t.Errorf("dst should have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) } } @@ -78,11 +88,13 @@ now := time.Now() dst := structWithTime{} src := structWithTime{now} - if err := Merge(&dst, src); err != nil { + + if err := mergo.Merge(&dst, src); err != nil { t.FailNow() } + if !dst.Birth.IsZero() { - t.Fatalf("dst should not have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) + t.Errorf("dst should not have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) } } @@ -90,10 +102,12 @@ now := time.Now() dst := structWithTime{} src := structWithTime{now} - if err := Merge(&dst, src, WithTransformers(timeTransfomer{})); err != nil { + + if err := mergo.Merge(&dst, src, mergo.WithTransformers(timeTransfomer{})); err != nil { t.FailNow() } + if dst.Birth.IsZero() { - t.Fatalf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) + t.Errorf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) } } diff --git a/issue61_test.go b/issue61_test.go index 8efa5e4..c94d693 100644 --- a/issue61_test.go +++ b/issue61_test.go @@ -1,8 +1,10 @@ -package mergo +package mergo_test import ( "reflect" "testing" + + "github.com/imdario/mergo" ) func TestIssue61MergeNilMap(t *testing.T) { @@ -11,9 +13,11 @@ } t1 := T{} t2 := T{I: map[string][]string{"hi": {"there"}}} - if err := Merge(&t1, t2); err != nil { + + if err := mergo.Merge(&t1, t2); err != nil { t.Fail() } + if !reflect.DeepEqual(t2, T{I: map[string][]string{"hi": {"there"}}}) { t.FailNow() } diff --git a/issue64_test.go b/issue64_test.go index 32382be..c3d2772 100644 --- a/issue64_test.go +++ b/issue64_test.go @@ -1,7 +1,9 @@ -package mergo +package mergo_test import ( "testing" + + "github.com/imdario/mergo" ) type Student struct { @@ -9,29 +11,35 @@ Books []string } -var testData = []struct { +type issue64TestData struct { S1 Student S2 Student ExpectedSlice []string -}{ - {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"a", "B"}}, - {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, - {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, - {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, +} + +func issue64Data() []issue64TestData { + return []issue64TestData{ + {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"a", "B"}}, + {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, + {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, + {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, + } } func TestIssue64MergeSliceWithOverride(t *testing.T) { - for _, data := range testData { - err := Merge(&data.S2, data.S1, WithOverride) + for _, data := range issue64Data() { + err := mergo.Merge(&data.S2, data.S1, mergo.WithOverride) if err != nil { t.Errorf("Error while merging %s", err) } + if len(data.S2.Books) != len(data.ExpectedSlice) { - t.Fatalf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) + t.Errorf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) } + for i, val := range data.S2.Books { if val != data.ExpectedSlice[i] { - t.Fatalf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) + t.Errorf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) } } } diff --git a/issue66_test.go b/issue66_test.go index 9e4bcce..643d4bb 100644 --- a/issue66_test.go +++ b/issue66_test.go @@ -1,7 +1,9 @@ -package mergo +package mergo_test import ( "testing" + + "github.com/imdario/mergo" ) type PrivateSliceTest66 struct { @@ -17,12 +19,15 @@ p2 := PrivateSliceTest66{ PublicStrings: []string{"six", "seven"}, } - if err := Merge(&p1, p2); err != nil { - t.Fatalf("Error during the merge: %v", err) + + if err := mergo.Merge(&p1, p2); err != nil { + t.Errorf("Error during the merge: %v", err) } + if len(p1.PublicStrings) != 3 { - t.Error("5 elements should be in 'PublicStrings' field") + t.Error("3 elements should be in 'PublicStrings' field, when no append") } + if len(p1.privateStrings) != 2 { t.Error("2 elements should be in 'privateStrings' field") } @@ -36,12 +41,15 @@ p2 := PrivateSliceTest66{ PublicStrings: []string{"six", "seven"}, } - if err := Merge(&p1, p2, WithAppendSlice); err != nil { - t.Fatalf("Error during the merge: %v", err) + + if err := mergo.Merge(&p1, p2, mergo.WithAppendSlice); err != nil { + t.Errorf("Error during the merge: %v", err) } + if len(p1.PublicStrings) != 5 { t.Error("5 elements should be in 'PublicStrings' field") } + if len(p1.privateStrings) != 2 { t.Error("2 elements should be in 'privateStrings' field") } diff --git a/issue83_test.go b/issue83_test.go new file mode 100644 index 0000000..d10e892 --- /dev/null +++ b/issue83_test.go @@ -0,0 +1,22 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +type issue83My struct { + Data []int +} + +func TestIssue83(t *testing.T) { + dst := issue83My{Data: []int{1, 2, 3}} + new := issue83My{} + if err := mergo.Merge(&dst, new, mergo.WithOverwriteWithEmptyValue); err != nil { + t.Error(err) + } + if len(dst.Data) > 0 { + t.Errorf("expected empty slice, got %v", dst.Data) + } +} diff --git a/issue84_test.go b/issue84_test.go index aa60526..b01ebf4 100644 --- a/issue84_test.go +++ b/issue84_test.go @@ -1,7 +1,9 @@ -package mergo +package mergo_test import ( "testing" + + "github.com/imdario/mergo" ) type DstStructIssue84 struct { @@ -27,9 +29,11 @@ p2 := map[string]interface{}{ "A": 3, "B": 4, "C": 0, } - if err := Map(&p1, p2, WithOverride); err != nil { - t.Fatalf("Error during the merge: %v", err) + + if err := mergo.Map(&p1, p2, mergo.WithOverride); err != nil { + t.Errorf("Error during the merge: %v", err) } + if p1.C != 0 { t.Error("C field should become '0'") } @@ -42,9 +46,11 @@ p2 := map[string]interface{}{ "A": 3, "B": 4, } - if err := Map(&p1, p2, WithOverride); err != nil { - t.Fatalf("Error during the merge: %v", err) + + if err := mergo.Map(&p1, p2, mergo.WithOverride); err != nil { + t.Errorf("Error during the merge: %v", err) } + if p1.C != 2 { t.Error("C field should be '2'") } @@ -65,9 +71,11 @@ "A": 0, "B": 0, "C": 5, }, "B": 4, "C": 0, } - if err := Map(&p1, p2, WithOverride); err != nil { - t.Fatalf("Error during the merge: %v", err) + + if err := mergo.Map(&p1, p2, mergo.WithOverride); err != nil { + t.Errorf("Error during the merge: %v", err) } + if p1.B != 4 { t.Error("A.C field should become '4'") } diff --git a/issue89_test.go b/issue89_test.go new file mode 100644 index 0000000..0a38994 --- /dev/null +++ b/issue89_test.go @@ -0,0 +1,57 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +func TestIssue89Boolean(t *testing.T) { + type Foo struct { + Bar bool `json:"bar"` + } + + src := Foo{Bar: true} + dst := Foo{Bar: false} + + if err := mergo.Merge(&dst, src); err != nil { + t.Error(err) + } + if dst.Bar == false { + t.Errorf("expected true, got false") + } +} + +func TestIssue89MergeWithEmptyValue(t *testing.T) { + p1 := map[string]interface{}{ + "A": 3, "B": "note", "C": true, + } + p2 := map[string]interface{}{ + "B": "", "C": false, + } + if err := mergo.Merge(&p1, p2, mergo.WithOverwriteWithEmptyValue); err != nil { + t.Error(err) + } + testCases := []struct { + key string + expected interface{} + }{ + { + "A", + 3, + }, + { + "B", + "", + }, + { + "C", + false, + }, + } + for _, tC := range testCases { + if p1[tC.key] != tC.expected { + t.Errorf("expected %v in p1[%q], got %v", tC.expected, tC.key, p1[tC.key]) + } + } +} diff --git a/issue90_test.go b/issue90_test.go new file mode 100644 index 0000000..872a327 --- /dev/null +++ b/issue90_test.go @@ -0,0 +1,43 @@ +package mergo_test + +import ( + "github.com/imdario/mergo" + "reflect" + "testing" +) + +type structWithStringMap struct { + Data map[string]string +} + + +func TestIssue90(t *testing.T) { + dst := map[string]structWithStringMap{ + "struct": { + Data: nil, + }, + } + src := map[string]structWithStringMap{ + "struct": { + Data: map[string]string{ + "foo": "bar", + }, + }, + } + expected := map[string]structWithStringMap{ + "struct": { + Data: map[string]string{ + "foo": "bar", + }, + }, + } + + err := mergo.Merge(&dst, src, mergo.WithOverride) + if err != nil { + t.Errorf("unexpected error %v", err) + } + + if !reflect.DeepEqual(dst, expected) { + t.Errorf("expected: %#v\ngot: %#v", expected, dst) + } +} diff --git a/issueXXX_test.go b/issueXXX_test.go new file mode 100644 index 0000000..e5f424f --- /dev/null +++ b/issueXXX_test.go @@ -0,0 +1,37 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +var testDataS = []struct { + S1 Student + S2 Student + ExpectedSlice []string +}{ + {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"1", "a", "B"}}, + {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, + {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, + {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, +} + +func TestMergeSliceWithOverrideWithAppendSlice(t *testing.T) { + for _, data := range testDataS { + err := mergo.Merge(&data.S2, data.S1, mergo.WithOverride, mergo.WithAppendSlice) + if err != nil { + t.Errorf("Error while merging %s", err) + } + + if len(data.S2.Books) != len(data.ExpectedSlice) { + t.Errorf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) + } + + for i, val := range data.S2.Books { + if val != data.ExpectedSlice[i] { + t.Errorf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) + } + } + } +} diff --git a/map.go b/map.go index 3f5afa8..a13a7ee 100644 --- a/map.go +++ b/map.go @@ -141,6 +141,9 @@ } func _map(dst, src interface{}, opts ...func(*Config)) error { + if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { + return ErrNonPointerAgument + } var ( vDst, vSrc reflect.Value err error diff --git a/merge.go b/merge.go index 3fb6c64..8c2a8fc 100644 --- a/merge.go +++ b/merge.go @@ -13,16 +13,28 @@ "reflect" ) -func hasExportedField(dst reflect.Value) (exported bool) { +func hasMergeableFields(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 || hasMergeableFields(dst.Field(i)) + } else if isExportedComponent(&field) { exported = exported || len(field.PkgPath) == 0 } } return +} + +func isExportedComponent(field *reflect.StructField) bool { + pkgPath := field.PkgPath + if len(pkgPath) > 0 { + return false + } + c := field.Name[0] + if 'a' <= c && c <= 'z' || c == '_' { + return false + } + return true } type Config struct { @@ -32,6 +44,8 @@ Transformers Transformers overwriteWithEmptyValue bool overwriteSliceWithEmptyValue bool + sliceDeepCopy bool + debug bool } type Transformers interface { @@ -46,7 +60,7 @@ typeCheck := config.TypeCheck overwriteWithEmptySrc := config.overwriteWithEmptyValue overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue - config.overwriteWithEmptyValue = false + sliceDeepCopy := config.sliceDeepCopy if !src.IsValid() { return @@ -74,21 +88,34 @@ switch dst.Kind() { case reflect.Struct: - if hasExportedField(dst) { + if hasMergeableFields(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) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) { + if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) { dst.Set(src) } } case reflect.Map: if dst.IsNil() && !src.IsNil() { - dst.Set(reflect.MakeMap(dst.Type())) - } + if dst.CanSet() { + dst.Set(reflect.MakeMap(dst.Type())) + } else { + dst = src + return + } + } + + if src.Kind() != reflect.Map { + if overwrite { + dst.Set(src) + } + return + } + for _, key := range src.MapKeys() { srcElement := src.MapIndex(key) if !srcElement.IsValid() { @@ -98,6 +125,9 @@ switch srcElement.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice: if srcElement.IsNil() { + if overwrite { + dst.SetMapIndex(key, srcElement) + } continue } fallthrough @@ -132,7 +162,7 @@ dstSlice = reflect.ValueOf(dstElement.Interface()) } - if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice { + if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { if typeCheck && srcSlice.Type() != dstSlice.Type() { return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) } @@ -142,6 +172,24 @@ return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) } dstSlice = reflect.AppendSlice(dstSlice, srcSlice) + } else if sliceDeepCopy { + i := 0 + for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ { + srcElement := srcSlice.Index(i) + dstElement := dstSlice.Index(i) + + if srcElement.CanInterface() { + srcElement = reflect.ValueOf(srcElement.Interface()) + } + if dstElement.CanInterface() { + dstElement = reflect.ValueOf(dstElement.Interface()) + } + + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } + } dst.SetMapIndex(key, dstSlice) } @@ -161,26 +209,35 @@ if !dst.CanSet() { break } - if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice { + if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { dst.Set(src) } else if config.AppendSlice { 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)) + } else if sliceDeepCopy { + for i := 0; i < src.Len() && i < dst.Len(); i++ { + srcElement := src.Index(i) + dstElement := dst.Index(i) + if srcElement.CanInterface() { + srcElement = reflect.ValueOf(srcElement.Interface()) + } + if dstElement.CanInterface() { + dstElement = reflect.ValueOf(dstElement.Interface()) + } + + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } } case reflect.Ptr: fallthrough case reflect.Interface: - if src.IsNil() { - break - } - - if dst.Kind() != reflect.Ptr && src.Type().AssignableTo(dst.Type()) { - if dst.IsNil() || overwrite { - if dst.CanSet() && (overwrite || isEmptyValue(dst)) { - dst.Set(src) - } + if isReflectNil(src) { + if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) { + dst.Set(src) } break } @@ -203,16 +260,28 @@ } break } + if dst.IsNil() || overwrite { if dst.CanSet() && (overwrite || isEmptyValue(dst)) { dst.Set(src) } - } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { - return + break + } + + if dst.Elem().Kind() == src.Elem().Kind() { + if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { + return + } + break } default: - if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) { - dst.Set(src) + mustSet := (isEmptyValue(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) + if mustSet { + if dst.CanSet() { + dst.Set(src) + } else { + dst = src + } } } @@ -246,7 +315,13 @@ config.Overwrite = true } -// WithOverride will make merge override empty dst slice with empty src slice. +// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values. +func WithOverwriteWithEmptyValue(config *Config) { + config.Overwrite = true + config.overwriteWithEmptyValue = true +} + +// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice. func WithOverrideEmptySlice(config *Config) { config.overwriteSliceWithEmptyValue = true } @@ -261,7 +336,16 @@ config.TypeCheck = true } +// WithSliceDeepCopy will merge slice element one by one with Overwrite flag. +func WithSliceDeepCopy(config *Config) { + config.sliceDeepCopy = true + config.Overwrite = true +} + func merge(dst, src interface{}, opts ...func(*Config)) error { + if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { + return ErrNonPointerAgument + } var ( vDst, vSrc reflect.Value err error @@ -281,3 +365,16 @@ } return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) } + +// IsReflectNil is the reflect value provided nil +func isReflectNil(v reflect.Value) bool { + k := v.Kind() + switch k { + case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr: + // Both interface and slice are nil if first word is 0. + // Both are always bigger than a word; assume flagIndir. + return v.IsNil() + default: + return false + } +} diff --git a/merge_appendslice_test.go b/merge_appendslice_test.go deleted file mode 100644 index a780f34..0000000 --- a/merge_appendslice_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package mergo - -import ( - "testing" -) - -var testDataS = []struct { - S1 Student - S2 Student - ExpectedSlice []string -}{ - {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"1", "a", "B"}}, - {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, - {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, - {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, -} - -func TestMergeSliceWithOverrideWithAppendSlice(t *testing.T) { - for _, data := range testDataS { - err := Merge(&data.S2, data.S1, WithOverride, WithAppendSlice) - if err != nil { - t.Errorf("Error while merging %s", err) - } - if len(data.S2.Books) != len(data.ExpectedSlice) { - t.Fatalf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) - } - for i, val := range data.S2.Books { - if val != data.ExpectedSlice[i] { - t.Fatalf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) - } - } - } -} diff --git a/merge_interface_concrete_test.go b/merge_interface_concrete_test.go deleted file mode 100644 index 9b5e82a..0000000 --- a/merge_interface_concrete_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package mergo - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -type ifaceTypesTest struct { - N int - Handler http.Handler -} - -type ifaceTypesHandler int - -func (*ifaceTypesHandler) ServeHTTP(rw http.ResponseWriter, _ *http.Request) { - rw.Header().Set("Test", "ifaceTypesHandler") -} - -func TestMergeInterfaceWithDifferentConcreteTypes(t *testing.T) { - dst := ifaceTypesTest{ - Handler: new(ifaceTypesHandler), - } - - src := ifaceTypesTest{ - N: 42, - Handler: http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { - rw.Header().Set("Test", "handlerFunc") - }), - } - - if err := Merge(&dst, src); err != nil { - t.Errorf("Error while merging %s", err) - } - - rw := httptest.NewRecorder() - dst.Handler.ServeHTTP(rw, nil) - - if got, want := rw.Header().Get("Test"), "ifaceTypesHandler"; got != want { - t.Errorf("Handler not merged in properly: got %q header value %q, want %q", "Test", got, want) - } -} diff --git a/merge_test.go b/merge_test.go index 5bf808a..dd51dab 100644 --- a/merge_test.go +++ b/merge_test.go @@ -1,8 +1,10 @@ -package mergo +package mergo_test import ( "reflect" "testing" + + "github.com/imdario/mergo" ) type transformer struct { @@ -29,7 +31,8 @@ func TestMergeWithTransformerNilStruct(t *testing.T) { a := foo{s: "foo"} b := foo{Bar: &bar{i: 2, s: map[string]string{"foo": "bar"}}} - if err := Merge(&a, &b, WithOverride, WithTransformers(&transformer{ + + if err := mergo.Merge(&a, &b, mergo.WithOverride, mergo.WithTransformers(&transformer{ m: map[reflect.Type]func(dst, src reflect.Value) error{ reflect.TypeOf(&bar{}): func(dst, src reflect.Value) error { // Do sthg with Elem @@ -39,12 +42,47 @@ }, }, })); err != nil { - t.Fatal(err) + t.Error(err) } + if a.s != "foo" { - t.Fatalf("b not merged in properly: a.s.Value(%s) != expected(%s)", a.s, "foo") + t.Errorf("b not merged in properly: a.s.Value(%s) != expected(%s)", a.s, "foo") } + if a.Bar == nil { - t.Fatalf("b not merged in properly: a.Bar shouldn't be nil") + t.Errorf("b not merged in properly: a.Bar shouldn't be nil") } } + +func TestMergeNonPointer(t *testing.T) { + dst := bar{ + i: 1, + } + src := bar{ + i: 2, + s: map[string]string{ + "a": "1", + }, + } + want := mergo.ErrNonPointerAgument + + if got := mergo.Merge(dst, src); got != want { + t.Errorf("want: %s, got: %s", want, got) + } +} + +func TestMapNonPointer(t *testing.T) { + dst := make(map[string]bar) + src := map[string]bar{ + "a": { + i: 2, + s: map[string]string{ + "a": "1", + }, + }, + } + want := mergo.ErrNonPointerAgument + if got := mergo.Merge(dst, src); got != want { + t.Errorf("want: %s, got: %s", want, got) + } +} diff --git a/mergo.go b/mergo.go index a82fea2..3cc926c 100644 --- a/mergo.go +++ b/mergo.go @@ -20,6 +20,7 @@ ErrNotSupported = errors.New("only structs and maps are supported") ErrExpectedMapAsDestination = errors.New("dst was expected to be a map") ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct") + ErrNonPointerAgument = errors.New("dst must be a pointer") ) // During deepMerge, must keep track of checks that are @@ -75,23 +76,3 @@ } return } - -// Traverses recursively both values, assigning src's fields values to dst. -// The map argument tracks comparisons that have already been seen, which allows -// short circuiting on recursive types. -func deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) { - if dst.CanAddr() { - addr := dst.UnsafeAddr() - h := 17 * addr - seen := visited[h] - typ := dst.Type() - for p := seen; p != nil; p = p.next { - if p.ptr == addr && p.typ == typ { - return nil - } - } - // Remember, remember... - visited[h] = &visit{addr, typ, seen} - } - return // TODO refactor -} diff --git a/mergo_test.go b/mergo_test.go index 10ec34c..d69714c 100644 --- a/mergo_test.go +++ b/mergo_test.go @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package mergo +package mergo_test import ( "io/ioutil" @@ -12,8 +12,7 @@ "testing" "time" - "github.com/stretchr/testify/assert" - + "github.com/imdario/mergo" "gopkg.in/yaml.v2" ) @@ -77,7 +76,9 @@ expected.Name = "B" expected.KeyValue = ekv - Merge(&b, a) + if err := mergo.Merge(&b, a); err != nil { + t.Error(err) + } if !reflect.DeepEqual(b, expected) { t.Errorf("Actual: %#v did not match \nExpected: %#v", b, expected) @@ -85,7 +86,7 @@ } func TestNil(t *testing.T) { - if err := Merge(nil, nil); err != ErrNilArguments { + if err := mergo.Merge(nil, nil); err != mergo.ErrNilArguments { t.Fail() } } @@ -93,7 +94,7 @@ func TestDifferentTypes(t *testing.T) { a := simpleTest{42} b := 42 - if err := Merge(&a, b); err != ErrDifferentArgumentsTypes { + if err := mergo.Merge(&a, b); err != mergo.ErrDifferentArgumentsTypes { t.Fail() } } @@ -101,11 +102,11 @@ func TestSimpleStruct(t *testing.T) { a := simpleTest{} b := simpleTest{42} - if err := Merge(&a, b); err != nil { + if err := mergo.Merge(&a, b); err != nil { t.FailNow() } if a.Value != 42 { - t.Fatalf("b not merged in properly: a.Value(%d) != b.Value(%d)", a.Value, b.Value) + t.Errorf("b not merged in properly: a.Value(%d) != b.Value(%d)", a.Value, b.Value) } if !reflect.DeepEqual(a, b) { t.FailNow() @@ -116,17 +117,17 @@ a := complexTest{} a.ID = "athing" b := complexTest{simpleTest{42}, 1, "bthing"} - if err := Merge(&a, b); err != nil { + if err := mergo.Merge(&a, b); err != nil { t.FailNow() } if a.St.Value != 42 { - t.Fatalf("b not merged in properly: a.St.Value(%d) != b.St.Value(%d)", a.St.Value, b.St.Value) + t.Errorf("b not merged in properly: a.St.Value(%d) != b.St.Value(%d)", a.St.Value, b.St.Value) } if a.sz == 1 { - t.Fatalf("a's private field sz not preserved from merge: a.sz(%d) == b.sz(%d)", a.sz, b.sz) + t.Errorf("a's private field sz not preserved from merge: a.sz(%d) == b.sz(%d)", a.sz, b.sz) } if a.ID == b.ID { - t.Fatalf("a's field ID merged unexpectedly: a.ID(%s) == b.ID(%s)", a.ID, b.ID) + t.Errorf("a's field ID merged unexpectedly: a.ID(%s) == b.ID(%s)", a.ID, b.ID) } } @@ -135,12 +136,12 @@ b := complexTest{simpleTest{42}, 2, ""} expect := complexTest{simpleTest{42}, 1, "do-not-overwrite-with-empty-value"} - if err := MergeWithOverwrite(&a, b); err != nil { + if err := mergo.MergeWithOverwrite(&a, b); err != nil { t.FailNow() } if !reflect.DeepEqual(a, expect) { - t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", a, expect) + t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", a, expect) } } @@ -149,11 +150,11 @@ s2 := simpleTest{19} a := pointerTest{&s1} b := pointerTest{&s2} - if err := Merge(&a, b); err != nil { + if err := mergo.Merge(&a, b); err != nil { t.FailNow() } if a.C.Value != b.C.Value { - t.Fatalf("b not merged in properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value) + t.Errorf("b not merged in properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value) } } @@ -207,7 +208,7 @@ } for _, test := range tests { - err := Merge(&test.dst, test.src) + err := mergo.Merge(&test.dst, test.src) if err != nil { t.Errorf("unexpected error: %v", err) continue @@ -221,54 +222,54 @@ func TestPointerStructNil(t *testing.T) { a := pointerTest{nil} b := pointerTest{&simpleTest{19}} - if err := Merge(&a, b); err != nil { + if err := mergo.Merge(&a, b); err != nil { t.FailNow() } if a.C.Value != b.C.Value { - t.Fatalf("b not merged in a properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value) - } -} - -func testSlice(t *testing.T, a []int, b []int, e []int, opts ...func(*Config)) { + t.Errorf("b not merged in a properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value) + } +} + +func testSlice(t *testing.T, a []int, b []int, e []int, opts ...func(*mergo.Config)) { t.Helper() bc := b sa := sliceTest{a} sb := sliceTest{b} - if err := Merge(&sa, sb, opts...); err != nil { + if err := mergo.Merge(&sa, sb, opts...); err != nil { t.FailNow() } if !reflect.DeepEqual(sb.S, bc) { - t.Fatalf("Source slice was modified %d != %d", sb.S, bc) + t.Errorf("Source slice was modified %d != %d", sb.S, bc) } if !reflect.DeepEqual(sa.S, e) { - t.Fatalf("b not merged in a proper way %d != %d", sa.S, e) + t.Errorf("b not merged in a proper way %d != %d", sa.S, e) } ma := map[string][]int{"S": a} mb := map[string][]int{"S": b} - if err := Merge(&ma, mb, opts...); err != nil { + if err := mergo.Merge(&ma, mb, opts...); err != nil { t.FailNow() } if !reflect.DeepEqual(mb["S"], bc) { - t.Fatalf("map value: Source slice was modified %d != %d", mb["S"], bc) + t.Errorf("map value: Source slice was modified %d != %d", mb["S"], bc) } if !reflect.DeepEqual(ma["S"], e) { - t.Fatalf("map value: b not merged in a proper way %d != %d", ma["S"], e) + t.Errorf("map value: b not merged in a proper way %d != %d", ma["S"], e) } if a == nil { // test case with missing dst key ma := map[string][]int{} mb := map[string][]int{"S": b} - if err := Merge(&ma, mb); err != nil { + if err := mergo.Merge(&ma, mb); err != nil { t.FailNow() } if !reflect.DeepEqual(mb["S"], bc) { - t.Fatalf("missing dst key: Source slice was modified %d != %d", mb["S"], bc) + t.Errorf("missing dst key: Source slice was modified %d != %d", mb["S"], bc) } if !reflect.DeepEqual(ma["S"], e) { - t.Fatalf("missing dst key: b not merged in a proper way %d != %d", ma["S"], e) + t.Errorf("missing dst key: b not merged in a proper way %d != %d", ma["S"], e) } } @@ -276,14 +277,14 @@ // test case with missing src key ma := map[string][]int{"S": a} mb := map[string][]int{} - if err := Merge(&ma, mb); err != nil { + if err := mergo.Merge(&ma, mb); err != nil { t.FailNow() } if !reflect.DeepEqual(mb["S"], bc) { - t.Fatalf("missing src key: Source slice was modified %d != %d", mb["S"], bc) + t.Errorf("missing src key: Source slice was modified %d != %d", mb["S"], bc) } if !reflect.DeepEqual(ma["S"], e) { - t.Fatalf("missing src key: b not merged in a proper way %d != %d", ma["S"], e) + t.Errorf("missing src key: b not merged in a proper way %d != %d", ma["S"], e) } } } @@ -294,12 +295,12 @@ testSlice(t, []int{1}, []int{2, 3}, []int{1}) testSlice(t, []int{1}, []int{}, []int{1}) testSlice(t, []int{1}, nil, []int{1}) - testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3}, WithAppendSlice) - testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3}, WithAppendSlice) - testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, WithAppendSlice) - testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, WithAppendSlice, WithOverride) - testSlice(t, []int{1}, []int{}, []int{1}, WithAppendSlice) - testSlice(t, []int{1}, nil, []int{1}, WithAppendSlice) + testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice) + testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice) + testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice) + 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) } func TestEmptyMaps(t *testing.T) { @@ -307,7 +308,7 @@ b := mapTest{ map[int]int{}, } - if err := Merge(&a, b); err != nil { + if err := mergo.Merge(&a, b); err != nil { t.Fail() } if !reflect.DeepEqual(a, b) { @@ -318,7 +319,7 @@ func TestEmptyToEmptyMaps(t *testing.T) { a := mapTest{} b := mapTest{} - if err := Merge(&a, b); err != nil { + if err := mergo.Merge(&a, b); err != nil { t.Fail() } if !reflect.DeepEqual(a, b) { @@ -338,7 +339,7 @@ b := mapTest{ map[int]int{}, } - if err := Merge(&a, b); err != nil { + if err := mergo.Merge(&a, b); err != nil { t.Fail() } if !reflect.DeepEqual(a, aa) { @@ -367,12 +368,12 @@ "e": {14}, } - if err := MergeWithOverwrite(&m, n); err != nil { - t.Fatalf(err.Error()) + if err := mergo.MergeWithOverwrite(&m, n); err != nil { + t.Errorf(err.Error()) } if !reflect.DeepEqual(m, expect) { - t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) + t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) } } @@ -397,13 +398,12 @@ "e": {14}, } - if err := Merge(&m, n, WithOverride); err != nil { - t.Fatalf(err.Error()) - } - - assert.Equalf(t, expect, m, "Test Failed") + if err := mergo.Merge(&m, n, mergo.WithOverride); err != nil { + t.Errorf(err.Error()) + } + if !reflect.DeepEqual(m, expect) { - t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) + t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) } } @@ -524,15 +524,15 @@ t.Run(tc.name, func(t *testing.T) { var err error if tc.overwrite { - err = Merge(tc.target, *tc.changes, WithOverride) + err = mergo.Merge(tc.target, *tc.changes, mergo.WithOverride) } else { - err = Merge(tc.target, *tc.changes) + err = mergo.Merge(tc.target, *tc.changes) } if err != nil { t.Error(err) } if !reflect.DeepEqual(tc.target, tc.output) { - t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", tc.target, tc.output) + t.Errorf("Test failed:\ngot :\n%+v\n\nwant :\n%+v\n\n", tc.target.Params, tc.output.Params) } }) } @@ -558,21 +558,21 @@ "e": {14}, } - if err := Merge(&m, n); err != nil { - t.Fatalf(err.Error()) + if err := mergo.Merge(&m, n); err != nil { + t.Errorf(err.Error()) } if !reflect.DeepEqual(m, expect) { - t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) + t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) } if m["a"].Value != 0 { - t.Fatalf(`n merged in m because I solved non-addressable map values TODO: m["a"].Value(%d) != n["a"].Value(%d)`, m["a"].Value, n["a"].Value) + t.Errorf(`n merged in m because I solved non-addressable map values TODO: m["a"].Value(%d) != n["a"].Value(%d)`, m["a"].Value, n["a"].Value) } if m["b"].Value != 42 { - t.Fatalf(`n wrongly merged in m: m["b"].Value(%d) != n["b"].Value(%d)`, m["b"].Value, n["b"].Value) + t.Errorf(`n wrongly merged in m: m["b"].Value(%d) != n["b"].Value(%d)`, m["b"].Value, n["b"].Value) } if m["c"].Value != 13 { - t.Fatalf(`n overwritten in m: m["c"].Value(%d) != n["c"].Value(%d)`, m["c"].Value, n["c"].Value) + t.Errorf(`n overwritten in m: m["c"].Value(%d) != n["c"].Value(%d)`, m["c"].Value, n["c"].Value) } } @@ -591,12 +591,12 @@ "c": nil, } - if err := Merge(&m, n, WithOverride); err != nil { - t.Fatalf(err.Error()) + if err := mergo.Merge(&m, n, mergo.WithOverride); err != nil { + t.Errorf(err.Error()) } if !reflect.DeepEqual(m, expect) { - t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) + t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) } } @@ -607,24 +607,24 @@ fl := license["fields"].(map[interface{}]interface{}) // license has one extra field (site) and another already existing in thing (author) that Mergo won't override. expectedLength := len(ft) + len(fl) - 1 - if err := Merge(&license, thing); err != nil { - t.Fatal(err.Error()) + if err := mergo.Merge(&license, thing); err != nil { + t.Error(err.Error()) } currentLength := len(license["fields"].(map[interface{}]interface{})) if currentLength != expectedLength { - t.Fatalf(`thing not merged in license properly, license must have %d elements instead of %d`, expectedLength, currentLength) + t.Errorf(`thing not merged in license properly, license must have %d elements instead of %d`, expectedLength, currentLength) } fields := license["fields"].(map[interface{}]interface{}) if _, ok := fields["id"]; !ok { - t.Fatalf(`thing not merged in license properly, license must have a new id field from thing`) + t.Errorf(`thing not merged in license properly, license must have a new id field from thing`) } } func TestTwoPointerValues(t *testing.T) { a := &simpleTest{} b := &simpleTest{42} - if err := Merge(a, b); err != nil { - t.Fatalf(`Boom. You crossed the streams: %s`, err) + if err := mergo.Merge(a, b); err != nil { + t.Errorf(`Boom. You crossed the streams: %s`, err) } } @@ -644,7 +644,7 @@ "zt": simpleTest{299}, // Mapping a missing field (zt doesn't exist) "nt": simpleTest{3}, } - if err := Map(&c, b); err != nil { + if err := mergo.Map(&c, b); err != nil { t.FailNow() } m := b["ct"].(map[string]interface{}) @@ -652,19 +652,19 @@ o := b["st"].(*simpleTest) p := b["nt"].(simpleTest) if c.Ct.St.Value != 42 { - t.Fatalf("b not merged in properly: c.Ct.St.Value(%d) != b.Ct.St.Value(%d)", c.Ct.St.Value, n["value"]) + t.Errorf("b not merged in properly: c.Ct.St.Value(%d) != b.Ct.St.Value(%d)", c.Ct.St.Value, n["value"]) } if c.St.Value != 144 { - t.Fatalf("b not merged in properly: c.St.Value(%d) != b.St.Value(%d)", c.St.Value, o.Value) + t.Errorf("b not merged in properly: c.St.Value(%d) != b.St.Value(%d)", c.St.Value, o.Value) } if c.Nt.Value != 3 { - t.Fatalf("b not merged in properly: c.Nt.Value(%d) != b.Nt.Value(%d)", c.St.Value, p.Value) + t.Errorf("b not merged in properly: c.Nt.Value(%d) != b.Nt.Value(%d)", c.St.Value, p.Value) } if c.Ct.sz == 1 { - t.Fatalf("a's private field sz not preserved from merge: c.Ct.sz(%d) == b.Ct.sz(%d)", c.Ct.sz, m["sz"]) + t.Errorf("a's private field sz not preserved from merge: c.Ct.sz(%d) == b.Ct.sz(%d)", c.Ct.sz, m["sz"]) } if c.Ct.ID == m["id"] { - t.Fatalf("a's field ID merged unexpectedly: c.Ct.ID(%s) == b.Ct.ID(%s)", c.Ct.ID, m["id"]) + t.Errorf("a's field ID merged unexpectedly: c.Ct.ID(%s) == b.Ct.ID(%s)", c.Ct.ID, m["id"]) } } @@ -673,22 +673,22 @@ b := map[string]interface{}{ "value": 42, } - if err := Map(&a, b); err != nil { + if err := mergo.Map(&a, b); err != nil { t.FailNow() } if a.Value != 42 { - t.Fatalf("b not merged in properly: a.Value(%d) != b.Value(%v)", a.Value, b["value"]) + t.Errorf("b not merged in properly: a.Value(%d) != b.Value(%v)", a.Value, b["value"]) } } func TestIfcMap(t *testing.T) { a := ifcTest{} b := ifcTest{42} - if err := Map(&a, b); err != nil { + if err := mergo.Map(&a, b); err != nil { t.FailNow() } if a.I != 42 { - t.Fatalf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) + t.Errorf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) } if !reflect.DeepEqual(a, b) { t.FailNow() @@ -698,22 +698,22 @@ func TestIfcMapNoOverwrite(t *testing.T) { a := ifcTest{13} b := ifcTest{42} - if err := Map(&a, b); err != nil { + if err := mergo.Map(&a, b); err != nil { t.FailNow() } if a.I != 13 { - t.Fatalf("a not left alone: a.I(%d) == b.I(%d)", a.I, b.I) + t.Errorf("a not left alone: a.I(%d) == b.I(%d)", a.I, b.I) } } func TestIfcMapWithOverwrite(t *testing.T) { a := ifcTest{13} b := ifcTest{42} - if err := MapWithOverwrite(&a, b); err != nil { + if err := mergo.MapWithOverwrite(&a, b); err != nil { t.FailNow() } if a.I != 42 { - t.Fatalf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) + t.Errorf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) } if !reflect.DeepEqual(a, b) { t.FailNow() @@ -729,7 +729,7 @@ func TestBackAndForth(t *testing.T) { pt := pointerMapTest{42, 1, &simpleTest{66}} m := make(map[string]interface{}) - if err := Map(&m, pt); err != nil { + if err := mergo.Map(&m, pt); err != nil { t.FailNow() } var ( @@ -737,27 +737,27 @@ ok bool ) if v, ok = m["a"]; v.(int) != pt.A || !ok { - t.Fatalf("pt not merged in properly: m[`a`](%d) != pt.A(%d)", v, pt.A) + t.Errorf("pt not merged in properly: m[`a`](%d) != pt.A(%d)", v, pt.A) } if v, ok = m["b"]; !ok { - t.Fatalf("pt not merged in properly: B is missing in m") + t.Errorf("pt not merged in properly: B is missing in m") } var st *simpleTest if st = v.(*simpleTest); st.Value != 66 { - t.Fatalf("something went wrong while mapping pt on m, B wasn't copied") + t.Errorf("something went wrong while mapping pt on m, B wasn't copied") } bpt := pointerMapTest{} - if err := Map(&bpt, m); err != nil { - t.Fatal(err) + if err := mergo.Map(&bpt, m); err != nil { + t.Error(err) } if bpt.A != pt.A { - t.Fatalf("pt not merged in properly: bpt.A(%d) != pt.A(%d)", bpt.A, pt.A) + t.Errorf("pt not merged in properly: bpt.A(%d) != pt.A(%d)", bpt.A, pt.A) } if bpt.hidden == pt.hidden { - t.Fatalf("pt unexpectedly merged: bpt.hidden(%d) == pt.hidden(%d)", bpt.hidden, pt.hidden) + t.Errorf("pt unexpectedly merged: bpt.hidden(%d) == pt.hidden(%d)", bpt.hidden, pt.hidden) } if bpt.B.Value != pt.B.Value { - t.Fatalf("pt not merged in properly: bpt.B.Value(%d) != pt.B.Value(%d)", bpt.B.Value, pt.B.Value) + t.Errorf("pt not merged in properly: bpt.B.Value(%d) != pt.B.Value(%d)", bpt.B.Value, pt.B.Value) } } @@ -774,11 +774,11 @@ } for _, test := range tests { pt := test.input - if err := MapWithOverwrite(&pt, m); err != nil { + if err := mergo.MapWithOverwrite(&pt, m); err != nil { t.FailNow() } if pt.B.Value != newValue { - t.Fatalf("pt not mapped properly: pt.A.Value(%d) != m[`b`][`value`](%d)", pt.B.Value, newValue) + t.Errorf("pt not mapped properly: pt.A.Value(%d) != m[`b`][`value`](%d)", pt.B.Value, newValue) } } @@ -797,21 +797,21 @@ "Birth": &now, } b := structWithTimePointer{} - if err := Merge(&b, dataStruct); err != nil { + if err := mergo.Merge(&b, dataStruct); err != nil { t.FailNow() } if b.Birth.IsZero() { - t.Fatalf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth) + t.Errorf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth) } if b.Birth != dataStruct.Birth { - t.Fatalf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth) + t.Errorf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth) } b = structWithTimePointer{} - if err := Map(&b, dataMap); err != nil { + if err := mergo.Map(&b, dataMap); err != nil { t.FailNow() } if b.Birth.IsZero() { - t.Fatalf("time.Time not merged in properly: b.Birth(%v) != dataMap['Birth'](%v)", b.Birth, dataMap["Birth"]) + t.Errorf("time.Time not merged in properly: b.Birth(%v) != dataMap['Birth'](%v)", b.Birth, dataMap["Birth"]) } } @@ -836,11 +836,11 @@ "x": {}, }, } - if err := Map(dst, src); err != nil { + if err := mergo.Map(dst, src); err != nil { t.FailNow() } if dst.NestedPtrValue["x"].A == 0 { - 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) + t.Errorf("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) } } @@ -871,7 +871,7 @@ t.Errorf("Should not have panicked") } }() - Merge(&a, b) + mergo.Merge(&a, b) } type structWithBoolPointer struct { @@ -886,31 +886,31 @@ dst := structWithBoolPointer{ &bf, } - if err := Merge(&dst, src); err != nil { + if err := mergo.Merge(&dst, src); err != nil { t.FailNow() } if dst.C == src.C { - t.Fatalf("dst.C should be a different pointer than src.C") + t.Errorf("dst.C should be a different pointer than src.C") } if *dst.C != *src.C { - t.Fatalf("dst.C should be true") + t.Errorf("dst.C should be true") } } func TestMergeMapWithInnerSliceOfDifferentType(t *testing.T) { testCases := []struct { name string - options []func(*Config) + options []func(*mergo.Config) err string }{ { "With override and append slice", - []func(*Config){WithOverride, WithAppendSlice}, + []func(*mergo.Config){mergo.WithOverride, mergo.WithAppendSlice}, "cannot append two slices with different type", }, { "With override and type check", - []func(*Config){WithOverride, WithTypeCheck}, + []func(*mergo.Config){mergo.WithOverride, mergo.WithTypeCheck}, "cannot override two slices with different type", }, } @@ -923,8 +923,8 @@ "foo": []int{1, 2}, } - if err := Merge(&src, &dst, tc.options...); err == nil || !strings.Contains(err.Error(), tc.err) { - t.Fatalf("expected %q, got %q", tc.err, err) + if err := mergo.Merge(&src, &dst, tc.options...); err == nil || !strings.Contains(err.Error(), tc.err) { + t.Errorf("expected %q, got %q", tc.err, err) } }) } @@ -934,7 +934,7 @@ src := []string{"a", "b"} dst := []int{1, 2} - if err := Merge(&src, &dst, WithOverride, WithAppendSlice); err != ErrNotSupported { - t.Fatalf("expected %q, got %q", ErrNotSupported, err) - } -} + if err := mergo.Merge(&src, &dst, mergo.WithOverride, mergo.WithAppendSlice); err != mergo.ErrNotSupported { + t.Errorf("expected %q, got %q", mergo.ErrNotSupported, err) + } +} diff --git a/pr80_test.go b/pr80_test.go index 0b3220f..64f2a78 100644 --- a/pr80_test.go +++ b/pr80_test.go @@ -1,7 +1,9 @@ -package mergo +package mergo_test import ( "testing" + + "github.com/imdario/mergo" ) type mapInterface map[string]interface{} @@ -9,10 +11,10 @@ func TestMergeMapsEmptyString(t *testing.T) { a := mapInterface{"s": ""} b := mapInterface{"s": "foo"} - if err := Merge(&a, b); err != nil { - t.Fatal(err) + if err := mergo.Merge(&a, b); err != nil { + t.Error(err) } if a["s"] != "foo" { - t.Fatalf("b not merged in properly: a.s.Value(%s) != expected(%s)", a["s"], "foo") + t.Errorf("b not merged in properly: a.s.Value(%s) != expected(%s)", a["s"], "foo") } } diff --git a/pr81_test.go b/pr81_test.go index e90e923..30abe74 100644 --- a/pr81_test.go +++ b/pr81_test.go @@ -1,7 +1,9 @@ -package mergo +package mergo_test import ( "testing" + + "github.com/imdario/mergo" ) func TestMapInterfaceWithMultipleLayer(t *testing.T) { @@ -18,15 +20,15 @@ }, } - if err := Map(&m1, m2, WithOverride); err != nil { - t.Fatalf("Error merging: %v", err) + if err := mergo.Map(&m1, m2, mergo.WithOverride); err != nil { + t.Errorf("Error merging: %v", err) } // Check overwrite of sub map works expected := "v2" actual := m1["k1"].(map[string]interface{})["k1.1"].(string) if actual != expected { - t.Fatalf("Expected %v but got %v", + t.Errorf("Expected %v but got %v", expected, actual) } @@ -35,7 +37,7 @@ expected = "v3" actual = m1["k1"].(map[string]interface{})["k1.2"].(string) if actual != expected { - t.Fatalf("Expected %v but got %v", + t.Errorf("Expected %v but got %v", expected, actual) } diff --git a/v039_bugs_test.go b/v039_bugs_test.go new file mode 100644 index 0000000..c470ed0 --- /dev/null +++ b/v039_bugs_test.go @@ -0,0 +1,92 @@ +package mergo_test + +import ( + "testing" + + "github.com/imdario/mergo" +) + +type inner struct { + A int +} + +type outer struct { + inner + B int +} + +func TestV039Issue139(t *testing.T) { + dst := outer{ + inner: inner{A: 1}, + B: 2, + } + src := outer{ + inner: inner{A: 10}, + B: 20, + } + err := mergo.MergeWithOverwrite(&dst, src) + if err != nil { + panic(err.Error()) + } + if dst.inner.A == 1 { + t.Errorf("expected %d, got %d", src.inner.A, dst.inner.A) + } +} + +func TestV039Issue152(t *testing.T) { + dst := map[string]interface{}{ + "properties": map[string]interface{}{ + "field1": map[string]interface{}{ + "type": "text", + }, + "field2": "ohai", + }, + } + src := map[string]interface{}{ + "properties": map[string]interface{}{ + "field1": "wrong", + }, + } + if err := mergo.Map(&dst, src, mergo.WithOverride); err != nil { + t.Error(err) + } +} + +type issue146Foo struct { + A string + B map[string]issue146Bar +} + +type issue146Bar struct { + C *string + D *string +} + +func TestV039Issue146(t *testing.T) { + var ( + s1 = "asd" + s2 = "sdf" + ) + dst := issue146Foo{ + A: "two", + B: map[string]issue146Bar{ + "foo": { + C: &s1, + }, + }, + } + src := issue146Foo{ + A: "one", + B: map[string]issue146Bar{ + "foo": { + D: &s2, + }, + }, + } + if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { + t.Error(err) + } + if dst.B["foo"].D == nil { + t.Errorf("expected %v, got nil", &s2) + } +}