├── .travis.yml ├── testdata ├── license.yml └── thing.yml ├── doc.go ├── LICENSE ├── merge.go ├── mergo.go ├── README.md ├── map.go └── mergo_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | install: go get -t 3 | -------------------------------------------------------------------------------- /testdata/license.yml: -------------------------------------------------------------------------------- 1 | import: ../../../../fossene/db/schema/thing.yml 2 | fields: 3 | site: string 4 | -------------------------------------------------------------------------------- /testdata/thing.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | id: int 3 | name: string 4 | parent: ref "datu:thing" 5 | status: enum(draft, public, private) 6 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | /* 7 | Package mergo merges same-type structs and maps by setting default values in zero-value fields. 8 | 9 | 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). 10 | 11 | Usage 12 | 13 | From my own work-in-progress project: 14 | 15 | type networkConfig struct { 16 | Protocol string 17 | Address string 18 | ServerType string `json: "server_type"` 19 | Port uint16 20 | } 21 | 22 | type FssnConfig struct { 23 | Network networkConfig 24 | } 25 | 26 | var fssnDefault = FssnConfig { 27 | networkConfig { 28 | "tcp", 29 | "127.0.0.1", 30 | "http", 31 | 31560, 32 | }, 33 | } 34 | 35 | // Inside a function [...] 36 | 37 | if err := mergo.Merge(&config, fssnDefault); err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | // More code [...] 42 | 43 | */ 44 | package mergo 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dario Castañé. All rights reserved. 2 | Copyright (c) 2012 The Go Authors. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /merge.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Based on src/pkg/reflect/deepequal.go from official 7 | // golang's stdlib. 8 | 9 | package mergo 10 | 11 | import ( 12 | "reflect" 13 | ) 14 | 15 | // Traverses recursively both values, assigning src's fields values to dst. 16 | // The map argument tracks comparisons that have already been seen, which allows 17 | // short circuiting on recursive types. 18 | func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) { 19 | if !src.IsValid() { 20 | return 21 | } 22 | if dst.CanAddr() { 23 | addr := dst.UnsafeAddr() 24 | h := 17 * addr 25 | seen := visited[h] 26 | typ := dst.Type() 27 | for p := seen; p != nil; p = p.next { 28 | if p.ptr == addr && p.typ == typ { 29 | return nil 30 | } 31 | } 32 | // Remember, remember... 33 | visited[h] = &visit{addr, typ, seen} 34 | } 35 | switch dst.Kind() { 36 | case reflect.Struct: 37 | for i, n := 0, dst.NumField(); i < n; i++ { 38 | if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1); err != nil { 39 | return 40 | } 41 | } 42 | case reflect.Map: 43 | for _, key := range src.MapKeys() { 44 | srcElement := src.MapIndex(key) 45 | if !srcElement.IsValid() { 46 | continue 47 | } 48 | dstElement := dst.MapIndex(key) 49 | switch reflect.TypeOf(srcElement.Interface()).Kind() { 50 | case reflect.Struct: 51 | fallthrough 52 | case reflect.Map: 53 | if err = deepMerge(dstElement, srcElement, visited, depth+1); err != nil { 54 | return 55 | } 56 | } 57 | if !dstElement.IsValid() { 58 | dst.SetMapIndex(key, srcElement) 59 | } 60 | } 61 | case reflect.Ptr: 62 | fallthrough 63 | case reflect.Interface: 64 | if src.IsNil() { 65 | break 66 | } else if dst.IsNil() { 67 | if dst.CanSet() && isEmptyValue(dst) { 68 | dst.Set(src) 69 | } 70 | } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1); err != nil { 71 | return 72 | } 73 | default: 74 | if dst.CanSet() && !isEmptyValue(src) { 75 | dst.Set(src) 76 | } 77 | } 78 | return 79 | } 80 | 81 | // Merge sets fields' values in dst from src if they have a zero 82 | // value of their type. 83 | // dst and src must be valid same-type structs and dst must be 84 | // a pointer to struct. 85 | // It won't merge unexported (private) fields and will do recursively 86 | // any exported field. 87 | func Merge(dst, src interface{}) error { 88 | var ( 89 | vDst, vSrc reflect.Value 90 | err error 91 | ) 92 | if vDst, vSrc, err = resolveValues(dst, src); err != nil { 93 | return err 94 | } 95 | if vDst.Type() != vSrc.Type() { 96 | return ErrDifferentArgumentsTypes 97 | } 98 | return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0) 99 | } 100 | -------------------------------------------------------------------------------- /mergo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Based on src/pkg/reflect/deepequal.go from official 7 | // golang's stdlib. 8 | 9 | package mergo 10 | 11 | import ( 12 | "errors" 13 | "reflect" 14 | ) 15 | 16 | // Errors reported by Mergo when it finds invalid arguments. 17 | var ( 18 | ErrNilArguments = errors.New("src and dst must not be nil") 19 | ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type") 20 | ErrNotSupported = errors.New("only structs and maps are supported") 21 | ErrExpectedMapAsDestination = errors.New("dst was expected to be a map") 22 | ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct") 23 | ) 24 | 25 | // During deepMerge, must keep track of checks that are 26 | // in progress. The comparison algorithm assumes that all 27 | // checks in progress are true when it reencounters them. 28 | // Visited are stored in a map indexed by 17 * a1 + a2; 29 | type visit struct { 30 | ptr uintptr 31 | typ reflect.Type 32 | next *visit 33 | } 34 | 35 | // From src/pkg/encoding/json. 36 | func isEmptyValue(v reflect.Value) bool { 37 | switch v.Kind() { 38 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 39 | return v.Len() == 0 40 | case reflect.Bool: 41 | return !v.Bool() 42 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 43 | return v.Int() == 0 44 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 45 | return v.Uint() == 0 46 | case reflect.Float32, reflect.Float64: 47 | return v.Float() == 0 48 | case reflect.Interface, reflect.Ptr: 49 | return v.IsNil() 50 | } 51 | return false 52 | } 53 | 54 | func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) { 55 | if dst == nil || src == nil { 56 | err = ErrNilArguments 57 | return 58 | } 59 | vDst = reflect.ValueOf(dst).Elem() 60 | if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map { 61 | err = ErrNotSupported 62 | return 63 | } 64 | vSrc = reflect.ValueOf(src) 65 | // We check if vSrc is a pointer to dereference it. 66 | if vSrc.Kind() == reflect.Ptr { 67 | vSrc = vSrc.Elem() 68 | } 69 | return 70 | } 71 | 72 | // Traverses recursively both values, assigning src's fields values to dst. 73 | // The map argument tracks comparisons that have already been seen, which allows 74 | // short circuiting on recursive types. 75 | func deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) { 76 | if dst.CanAddr() { 77 | addr := dst.UnsafeAddr() 78 | h := 17 * addr 79 | seen := visited[h] 80 | typ := dst.Type() 81 | for p := seen; p != nil; p = p.next { 82 | if p.ptr == addr && p.typ == typ { 83 | return nil 84 | } 85 | } 86 | // Remember, remember... 87 | visited[h] = &visit{addr, typ, seen} 88 | } 89 | return // TODO refactor 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mergo 2 | 3 | A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. 4 | 5 | Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region Marche. 6 | 7 | ![Mergo dall'alto](http://www.comune.mergo.an.it/Siti/Mergo/Immagini/Foto/mergo_dall_alto.jpg) 8 | 9 | ## Status 10 | 11 | It is ready for production use. It works fine although it may use more of testing. Here some projects in the wild using Mergo: 12 | 13 | - [GoogleCloudPlatform/kubernetes](https://github.com/GoogleCloudPlatform/kubernetes) 14 | - [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy) 15 | - [EagerIO/Stout](https://github.com/EagerIO/Stout) 16 | - [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api) 17 | - [russross/canvasassignments](https://github.com/russross/canvasassignments) 18 | - [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api) 19 | - [casualjim/exeggutor](https://github.com/casualjim/exeggutor) 20 | - [divshot/gitling](https://github.com/divshot/gitling) 21 | - [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl) 22 | 23 | [![Build Status][1]][2] 24 | [![GoDoc](https://godoc.org/github.com/imdario/mergo?status.svg)](https://godoc.org/github.com/imdario/mergo) 25 | 26 | [1]: https://travis-ci.org/imdario/mergo.png 27 | [2]: https://travis-ci.org/imdario/mergo 28 | 29 | ## Installation 30 | 31 | go get github.com/imdario/mergo 32 | 33 | // use in your .go code 34 | import ( 35 | "github.com/imdario/mergo" 36 | ) 37 | 38 | ## Usage 39 | 40 | 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. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). 41 | 42 | if err := mergo.Merge(&dst, src); err != nil { 43 | // ... 44 | } 45 | 46 | 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. 47 | 48 | if err := mergo.Map(&dst, srcMap); err != nil { 49 | // ... 50 | } 51 | 52 | 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. 53 | 54 | More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo). 55 | 56 | Note: if test are failing due missing package, please execute: 57 | 58 | go get gopkg.in/yaml.v1 59 | 60 | ## Contact me 61 | 62 | 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): [@im_dario](https://twitter.com/im_dario) 63 | 64 | ## About 65 | 66 | Written by [Dario Castañé](http://dario.im). 67 | 68 | ## License 69 | 70 | [BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE). 71 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Based on src/pkg/reflect/deepequal.go from official 7 | // golang's stdlib. 8 | 9 | package mergo 10 | 11 | import ( 12 | "fmt" 13 | "reflect" 14 | "unicode" 15 | "unicode/utf8" 16 | ) 17 | 18 | func changeInitialCase(s string, mapper func(rune) rune) string { 19 | if s == "" { 20 | return s 21 | } 22 | r, n := utf8.DecodeRuneInString(s) 23 | return string(mapper(r)) + s[n:] 24 | } 25 | 26 | func isExported(field reflect.StructField) bool { 27 | r, _ := utf8.DecodeRuneInString(field.Name) 28 | return r >= 'A' && r <= 'Z' 29 | } 30 | 31 | // Traverses recursively both values, assigning src's fields values to dst. 32 | // The map argument tracks comparisons that have already been seen, which allows 33 | // short circuiting on recursive types. 34 | func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) { 35 | if dst.CanAddr() { 36 | addr := dst.UnsafeAddr() 37 | h := 17 * addr 38 | seen := visited[h] 39 | typ := dst.Type() 40 | for p := seen; p != nil; p = p.next { 41 | if p.ptr == addr && p.typ == typ { 42 | return nil 43 | } 44 | } 45 | // Remember, remember... 46 | visited[h] = &visit{addr, typ, seen} 47 | } 48 | zeroValue := reflect.Value{} 49 | switch dst.Kind() { 50 | case reflect.Map: 51 | dstMap := dst.Interface().(map[string]interface{}) 52 | for i, n := 0, src.NumField(); i < n; i++ { 53 | srcType := src.Type() 54 | field := srcType.Field(i) 55 | if !isExported(field) { 56 | continue 57 | } 58 | fieldName := field.Name 59 | fieldName = changeInitialCase(fieldName, unicode.ToLower) 60 | if v, ok := dstMap[fieldName]; !ok || isEmptyValue(reflect.ValueOf(v)) { 61 | dstMap[fieldName] = src.Field(i).Interface() 62 | } 63 | } 64 | case reflect.Struct: 65 | srcMap := src.Interface().(map[string]interface{}) 66 | for key := range srcMap { 67 | srcValue := srcMap[key] 68 | fieldName := changeInitialCase(key, unicode.ToUpper) 69 | dstElement := dst.FieldByName(fieldName) 70 | if dstElement == zeroValue { 71 | // We discard it because the field doesn't exist. 72 | continue 73 | } 74 | srcElement := reflect.ValueOf(srcValue) 75 | dstKind := dstElement.Kind() 76 | srcKind := srcElement.Kind() 77 | if srcKind == reflect.Ptr && dstKind != reflect.Ptr { 78 | srcElement = srcElement.Elem() 79 | srcKind = reflect.TypeOf(srcElement.Interface()).Kind() 80 | } else if dstKind == reflect.Ptr { 81 | // Can this work? I guess it can't. 82 | if srcKind != reflect.Ptr && srcElement.CanAddr() { 83 | srcPtr := srcElement.Addr() 84 | srcElement = reflect.ValueOf(srcPtr) 85 | srcKind = reflect.Ptr 86 | } 87 | } 88 | if !srcElement.IsValid() { 89 | continue 90 | } 91 | if srcKind == dstKind { 92 | if err = deepMerge(dstElement, srcElement, visited, depth+1); err != nil { 93 | return 94 | } 95 | } else { 96 | if srcKind == reflect.Map { 97 | if err = deepMap(dstElement, srcElement, visited, depth+1); err != nil { 98 | return 99 | } 100 | } else { 101 | return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) 102 | } 103 | } 104 | } 105 | } 106 | return 107 | } 108 | 109 | // Map sets fields' values in dst from src. 110 | // src can be a map with string keys or a struct. dst must be the opposite: 111 | // if src is a map, dst must be a valid pointer to struct. If src is a struct, 112 | // dst must be map[string]interface{}. 113 | // It won't merge unexported (private) fields and will do recursively 114 | // any exported field. 115 | // If dst is a map, keys will be src fields' names in lower camel case. 116 | // Missing key in src that doesn't match a field in dst will be skipped. This 117 | // doesn't apply if dst is a map. 118 | // This is separated method from Merge because it is cleaner and it keeps sane 119 | // semantics: merging equal types, mapping different (restricted) types. 120 | func Map(dst, src interface{}) error { 121 | var ( 122 | vDst, vSrc reflect.Value 123 | err error 124 | ) 125 | if vDst, vSrc, err = resolveValues(dst, src); err != nil { 126 | return err 127 | } 128 | // To be friction-less, we redirect equal-type arguments 129 | // to deepMerge. Only because arguments can be anything. 130 | if vSrc.Kind() == vDst.Kind() { 131 | return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0) 132 | } 133 | switch vSrc.Kind() { 134 | case reflect.Struct: 135 | if vDst.Kind() != reflect.Map { 136 | return ErrExpectedMapAsDestination 137 | } 138 | case reflect.Map: 139 | if vDst.Kind() != reflect.Struct { 140 | return ErrExpectedStructAsDestination 141 | } 142 | default: 143 | return ErrNotSupported 144 | } 145 | return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0) 146 | } 147 | -------------------------------------------------------------------------------- /mergo_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package mergo 7 | 8 | import ( 9 | "gopkg.in/yaml.v1" 10 | "io/ioutil" 11 | "reflect" 12 | "testing" 13 | ) 14 | 15 | type simpleTest struct { 16 | Value int 17 | } 18 | 19 | type complexTest struct { 20 | St simpleTest 21 | sz int 22 | Id string 23 | } 24 | 25 | type moreComplextText struct { 26 | Ct complexTest 27 | St simpleTest 28 | Nt simpleTest 29 | } 30 | 31 | type pointerTest struct { 32 | C *simpleTest 33 | } 34 | 35 | type sliceTest struct { 36 | S []int 37 | } 38 | 39 | func TestNil(t *testing.T) { 40 | if err := Merge(nil, nil); err != ErrNilArguments { 41 | t.Fail() 42 | } 43 | } 44 | 45 | func TestDifferentTypes(t *testing.T) { 46 | a := simpleTest{42} 47 | b := 42 48 | if err := Merge(&a, b); err != ErrDifferentArgumentsTypes { 49 | t.Fail() 50 | } 51 | } 52 | 53 | func TestSimpleStruct(t *testing.T) { 54 | a := simpleTest{} 55 | b := simpleTest{42} 56 | if err := Merge(&a, b); err != nil { 57 | t.FailNow() 58 | } 59 | if a.Value != 42 { 60 | t.Fatalf("b not merged in a properly: a.Value(%d) != b.Value(%d)", a.Value, b.Value) 61 | } 62 | if !reflect.DeepEqual(a, b) { 63 | t.FailNow() 64 | } 65 | } 66 | 67 | func TestComplexStruct(t *testing.T) { 68 | a := complexTest{} 69 | a.Id = "athing" 70 | b := complexTest{simpleTest{42}, 1, "bthing"} 71 | if err := Merge(&a, b); err != nil { 72 | t.FailNow() 73 | } 74 | if a.St.Value != 42 { 75 | t.Fatalf("b not merged in a properly: a.St.Value(%d) != b.St.Value(%d)", a.St.Value, b.St.Value) 76 | } 77 | if a.sz == 1 { 78 | t.Fatalf("a's private field sz not preserved from merge: a.sz(%d) == b.sz(%d)", a.sz, b.sz) 79 | } 80 | if a.Id != b.Id { 81 | t.Fatalf("a's field Id not merged properly: a.Id(%s) != b.Id(%s)", a.Id, b.Id) 82 | } 83 | } 84 | 85 | func TestPointerStruct(t *testing.T) { 86 | s1 := simpleTest{} 87 | s2 := simpleTest{19} 88 | a := pointerTest{&s1} 89 | b := pointerTest{&s2} 90 | if err := Merge(&a, b); err != nil { 91 | t.FailNow() 92 | } 93 | if a.C.Value != b.C.Value { 94 | //t.Fatalf("b not merged in a properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value) 95 | } 96 | } 97 | 98 | func TestPointerStructNil(t *testing.T) { 99 | a := pointerTest{nil} 100 | b := pointerTest{&simpleTest{19}} 101 | if err := Merge(&a, b); err != nil { 102 | t.FailNow() 103 | } 104 | if a.C.Value != b.C.Value { 105 | t.Fatalf("b not merged in a properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value) 106 | } 107 | } 108 | 109 | func TestSliceStruct(t *testing.T) { 110 | a := sliceTest{} 111 | b := sliceTest{[]int{1, 2, 3}} 112 | if err := Merge(&a, b); err != nil { 113 | t.FailNow() 114 | } 115 | if len(b.S) != 3 { 116 | t.FailNow() 117 | } 118 | if len(a.S) != len(b.S) { 119 | t.Fatalf("b not merged in a properly %d != %d", len(a.S), len(b.S)) 120 | } 121 | 122 | a = sliceTest{[]int{1}} 123 | b = sliceTest{[]int{1, 2, 3}} 124 | if err := Merge(&a, b); err != nil { 125 | t.FailNow() 126 | } 127 | if len(b.S) != 3 { 128 | t.FailNow() 129 | } 130 | if len(a.S) != len(b.S) { 131 | t.Fatalf("b not merged in a properly %d != %d", len(a.S), len(b.S)) 132 | } 133 | } 134 | 135 | func TestMaps(t *testing.T) { 136 | m := map[string]simpleTest{ 137 | "a": simpleTest{}, 138 | "b": simpleTest{42}, 139 | } 140 | n := map[string]simpleTest{ 141 | "a": simpleTest{16}, 142 | "b": simpleTest{}, 143 | "c": simpleTest{12}, 144 | } 145 | if err := Merge(&m, n); err != nil { 146 | t.Fatalf(err.Error()) 147 | } 148 | if len(m) != 3 { 149 | t.Fatalf(`n not merged in m properly, m must have 3 elements instead of %d`, len(m)) 150 | } 151 | if m["a"].Value != 0 { 152 | 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) 153 | } 154 | if m["b"].Value != 42 { 155 | t.Fatalf(`n wrongly merged in m: m["b"].Value(%d) != n["b"].Value(%d)`, m["b"].Value, n["b"].Value) 156 | } 157 | if m["c"].Value != 12 { 158 | t.Fatalf(`n not merged in m: m["c"].Value(%d) != n["c"].Value(%d)`, m["c"].Value, n["c"].Value) 159 | } 160 | } 161 | 162 | func TestYAMLMaps(t *testing.T) { 163 | thing := loadYAML("testdata/thing.yml") 164 | license := loadYAML("testdata/license.yml") 165 | ft := thing["fields"].(map[interface{}]interface{}) 166 | fl := license["fields"].(map[interface{}]interface{}) 167 | expectedLength := len(ft) + len(fl) 168 | if err := Merge(&license, thing); err != nil { 169 | t.Fatal(err.Error()) 170 | } 171 | currentLength := len(license["fields"].(map[interface{}]interface{})) 172 | if currentLength != expectedLength { 173 | t.Fatalf(`thing not merged in license properly, license must have %d elements instead of %d`, expectedLength, currentLength) 174 | } 175 | fields := license["fields"].(map[interface{}]interface{}) 176 | if _, ok := fields["id"]; !ok { 177 | t.Fatalf(`thing not merged in license properly, license must have a new id field from thing`) 178 | } 179 | } 180 | 181 | func TestTwoPointerValues(t *testing.T) { 182 | a := &simpleTest{} 183 | b := &simpleTest{42} 184 | if err := Merge(a, b); err != nil { 185 | t.Fatalf(`Boom. You crossed the streams: %s`, err) 186 | } 187 | } 188 | 189 | func TestMap(t *testing.T) { 190 | a := complexTest{} 191 | a.Id = "athing" 192 | c := moreComplextText{a, simpleTest{}, simpleTest{}} 193 | b := map[string]interface{}{ 194 | "ct": map[string]interface{}{ 195 | "st": map[string]interface{}{ 196 | "value": 42, 197 | }, 198 | "sz": 1, 199 | "id": "bthing", 200 | }, 201 | "st": &simpleTest{144}, // Mapping a reference 202 | "zt": simpleTest{299}, // Mapping a missing field (zt doesn't exist) 203 | "nt": simpleTest{3}, 204 | } 205 | if err := Map(&c, b); err != nil { 206 | t.FailNow() 207 | } 208 | m := b["ct"].(map[string]interface{}) 209 | n := m["st"].(map[string]interface{}) 210 | o := b["st"].(*simpleTest) 211 | p := b["nt"].(simpleTest) 212 | if c.Ct.St.Value != 42 { 213 | t.Fatalf("b not merged in a properly: c.Ct.St.Value(%d) != b.Ct.St.Value(%d)", c.Ct.St.Value, n["value"]) 214 | } 215 | if c.St.Value != 144 { 216 | t.Fatalf("b not merged in a properly: c.St.Value(%d) != b.St.Value(%d)", c.St.Value, o.Value) 217 | } 218 | if c.Nt.Value != 3 { 219 | t.Fatalf("b not merged in a properly: c.Nt.Value(%d) != b.Nt.Value(%d)", c.St.Value, p.Value) 220 | } 221 | if c.Ct.sz == 1 { 222 | t.Fatalf("a's private field sz not preserved from merge: c.Ct.sz(%d) == b.Ct.sz(%d)", c.Ct.sz, m["sz"]) 223 | } 224 | if c.Ct.Id != m["id"] { 225 | t.Fatalf("a's field Id not merged properly: c.Ct.Id(%s) != b.Ct.Id(%s)", c.Ct.Id, m["id"]) 226 | } 227 | } 228 | 229 | func TestSimpleMap(t *testing.T) { 230 | a := simpleTest{} 231 | b := map[string]interface{}{ 232 | "value": 42, 233 | } 234 | if err := Map(&a, b); err != nil { 235 | t.FailNow() 236 | } 237 | if a.Value != 42 { 238 | t.Fatalf("b not merged in a properly: a.Value(%d) != b.Value(%v)", a.Value, b["value"]) 239 | } 240 | } 241 | 242 | type pointerMapTest struct { 243 | A int 244 | hidden int 245 | B *simpleTest 246 | } 247 | 248 | func TestBackAndForth(t *testing.T) { 249 | pt := pointerMapTest{42, 1, &simpleTest{66}} 250 | m := make(map[string]interface{}) 251 | if err := Map(&m, pt); err != nil { 252 | t.FailNow() 253 | } 254 | var ( 255 | v interface{} 256 | ok bool 257 | ) 258 | if v, ok = m["a"]; v.(int) != pt.A || !ok { 259 | t.Fatalf("pt not merged properly: m[`a`](%d) != pt.A(%d)", v, pt.A) 260 | } 261 | if v, ok = m["b"]; !ok { 262 | t.Fatalf("pt not merged properly: B is missing in m") 263 | } 264 | var st *simpleTest 265 | if st = v.(*simpleTest); st.Value != 66 { 266 | t.Fatalf("something went wrong while mapping pt on m, B wasn't copied") 267 | } 268 | bpt := pointerMapTest{} 269 | if err := Map(&bpt, m); err != nil { 270 | t.Fatal(err) 271 | } 272 | if bpt.A != pt.A { 273 | t.Fatalf("pt not merged properly: bpt.A(%d) != pt.A(%d)", bpt.A, pt.A) 274 | } 275 | if bpt.hidden == pt.hidden { 276 | t.Fatalf("pt unexpectedly merged: bpt.hidden(%d) == pt.hidden(%d)", bpt.hidden, pt.hidden) 277 | } 278 | if bpt.B.Value != pt.B.Value { 279 | t.Fatalf("pt not merged properly: bpt.B.Value(%d) != pt.B.Value(%d)", bpt.B.Value, pt.B.Value) 280 | } 281 | } 282 | 283 | func loadYAML(path string) (m map[string]interface{}) { 284 | m = make(map[string]interface{}) 285 | raw, _ := ioutil.ReadFile(path) 286 | _ = yaml.Unmarshal(raw, &m) 287 | return 288 | } 289 | --------------------------------------------------------------------------------