├── README.md ├── merge ├── merge.go └── merge_test.go └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | # go-merge 2 | go-merge is a golang library for recursively merging data. It will merge and overwrite maps, structs, and other basic golang types 3 | as long as the base data and the override data are of a similar structure. 4 | 5 | ## Usage 6 | 7 | To use this package first you must install it: 8 | 9 | ``` 10 | go get github.com/divideandconquer/go-merge/merge 11 | ``` 12 | 13 | Then you can import it in your code and use it as follows: 14 | 15 | ```golang 16 | package main 17 | 18 | import ( 19 | "log" 20 | "github.com/divideandconquer/go-merge/merge" 21 | ) 22 | 23 | func main() { 24 | baseData := make(map[string]string) 25 | baseData["test"] = "foo" 26 | baseData["test2"] = "bar" 27 | 28 | overrideData := make(map[string]string) 29 | overrideData["test2"] = "override" 30 | 31 | result := merge.Merge(baseData, overrideData) 32 | 33 | log.Printf("%#v", result) // prints: map[string]string{"test":"foo", "test2":"override"} 34 | } 35 | 36 | ``` 37 | 38 | For more examples of usage check the [unit tests](/merge/merge_test.go). 39 | 40 | ## Contributing 41 | 42 | 1. Fork it 43 | 2. Create your feature branch (`git checkout -b my-new-feature`) 44 | 3. Add tests for your changes 45 | 4. Test your changes (`go test merge/*.go`) 46 | 5. Commit your changes (`git commit -am 'Add some feature'`) 47 | 6. Push to the branch (`git push origin my-new-feature`) 48 | 7. Create new Pull Request 49 | 50 | ## License 51 | This library is licensed using the Apache-2.0 [License](/LICENSE): 52 | 53 | ``` 54 | Copyright (c) 2015, Kyle Boorky 55 | ``` -------------------------------------------------------------------------------- /merge/merge.go: -------------------------------------------------------------------------------- 1 | package merge 2 | 3 | import "reflect" 4 | 5 | // Merge will take two data sets and merge them together - returning a new data set 6 | func Merge(base, override interface{}) interface{} { 7 | //reflect and recurse 8 | b := reflect.ValueOf(base) 9 | o := reflect.ValueOf(override) 10 | ret := mergeRecursive(b, o) 11 | 12 | return ret.Interface() 13 | } 14 | 15 | func mergeRecursive(base, override reflect.Value) reflect.Value { 16 | var result reflect.Value 17 | 18 | switch base.Kind() { 19 | case reflect.Ptr: 20 | switch base.Elem().Kind() { 21 | case reflect.Ptr: 22 | fallthrough 23 | case reflect.Interface: 24 | fallthrough 25 | case reflect.Struct: 26 | fallthrough 27 | case reflect.Map: 28 | // Pointers to complex types should recurse if they aren't nil 29 | if base.IsNil() { 30 | result = override 31 | } else if override.IsNil() { 32 | result = base 33 | } else { 34 | result = mergeRecursive(base.Elem(), override.Elem()) 35 | } 36 | default: 37 | // Pointers to basic types should just override 38 | if isEmptyValue(override) { 39 | result = base 40 | } else { 41 | result = override 42 | } 43 | } 44 | case reflect.Interface: 45 | // Interfaces should just be unwrapped and recursed through 46 | result = mergeRecursive(base.Elem(), override.Elem()) 47 | 48 | case reflect.Struct: 49 | // For structs we loop over fields and recurse 50 | // setup our result struct 51 | result = reflect.New(base.Type()) 52 | for i, n := 0, base.NumField(); i < n; i++ { 53 | // We cant set private fields so don't recurse on them 54 | if result.Elem().Field(i).CanSet() { 55 | // get the merged value of each field 56 | newVal := mergeRecursive(base.Field(i), override.Field(i)) 57 | 58 | //attempt to set that merged value on our result struct 59 | if result.Elem().Field(i).CanSet() && newVal.IsValid() { 60 | if newVal.Kind() == reflect.Ptr && result.Elem().Field(i).Kind() != reflect.Ptr { 61 | newVal = newVal.Elem() 62 | } else if result.Elem().Field(i).Kind() == reflect.Ptr && newVal.Kind() != reflect.Ptr && newVal.CanAddr() { 63 | newVal = newVal.Addr() 64 | } 65 | result.Elem().Field(i).Set(newVal) 66 | } 67 | } 68 | } 69 | 70 | case reflect.Map: 71 | // For Maps we copy the base data, and then replace it with merged data 72 | // We use two for loops to make sure all map keys from base and all keys from 73 | // override exist in the result just in case one of the maps is sparse. 74 | elementsAreValues := base.Type().Elem().Kind() != reflect.Ptr 75 | 76 | result = reflect.MakeMap(base.Type()) 77 | // Copy from base first 78 | for _, key := range base.MapKeys() { 79 | result.SetMapIndex(key, base.MapIndex(key)) 80 | } 81 | 82 | // Override with values from override if they exist 83 | if override.Kind() == reflect.Map { 84 | for _, key := range override.MapKeys() { 85 | overrideVal := override.MapIndex(key) 86 | baseVal := base.MapIndex(key) 87 | if !overrideVal.IsValid() { 88 | continue 89 | } 90 | 91 | // if there is no base value, just set the override 92 | if !baseVal.IsValid() { 93 | result.SetMapIndex(key, overrideVal) 94 | continue 95 | } 96 | 97 | // Merge the values and set in the result 98 | newVal := mergeRecursive(baseVal, overrideVal) 99 | if elementsAreValues && newVal.Kind() == reflect.Ptr { 100 | result.SetMapIndex(key, newVal.Elem()) 101 | 102 | } else { 103 | result.SetMapIndex(key, newVal) 104 | } 105 | } 106 | } 107 | 108 | default: 109 | // These are all generic types 110 | // override will be taken for generic types if it is set 111 | if isEmptyValue(override) { 112 | result = base 113 | } else { 114 | result = override 115 | } 116 | } 117 | return result 118 | } 119 | 120 | // Copied From http://golang.org/src/encoding/json/encode.go 121 | // Lines 280 - 296 122 | func isEmptyValue(v reflect.Value) bool { 123 | switch v.Kind() { 124 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 125 | return v.Len() == 0 126 | case reflect.Bool: 127 | return !v.Bool() 128 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 129 | return v.Int() == 0 130 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 131 | return v.Uint() == 0 132 | case reflect.Float32, reflect.Float64: 133 | return v.Float() == 0 134 | case reflect.Interface, reflect.Ptr: 135 | return v.IsNil() 136 | } 137 | return false 138 | } 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Kyle Boorky. 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "{}" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright {yyyy} {name of copyright owner} 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. -------------------------------------------------------------------------------- /merge/merge_test.go: -------------------------------------------------------------------------------- 1 | package merge 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/divideandconquer/go-merge/merge" 8 | ) 9 | 10 | func makeStringPtr(v string) *string { 11 | return &v 12 | } 13 | 14 | func TestUnit_Merge_BasePath(t *testing.T) { 15 | a := "" 16 | b := "foo" 17 | expected := "foo" 18 | ret := Merge(a, b) 19 | 20 | if !reflect.DeepEqual(ret, expected) { 21 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 22 | } 23 | } 24 | 25 | func TestUnit_Merge_AlternatePath_StringNotSetB(t *testing.T) { 26 | a := "bar" 27 | var b string 28 | expected := "bar" 29 | ret := Merge(a, b) 30 | 31 | if !reflect.DeepEqual(ret, expected) { 32 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 33 | } 34 | } 35 | 36 | func TestUnit_Merge_AlternatePath_StringNotSetA(t *testing.T) { 37 | var a string 38 | b := "bar" 39 | expected := "bar" 40 | ret := Merge(a, b) 41 | 42 | if !reflect.DeepEqual(ret, expected) { 43 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 44 | } 45 | } 46 | 47 | func TestUnit_Merge_AlternatePath_StringEmptyB(t *testing.T) { 48 | a := "bar" 49 | b := "" 50 | expected := "bar" 51 | ret := Merge(a, b) 52 | 53 | if !reflect.DeepEqual(ret, expected) { 54 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 55 | } 56 | } 57 | 58 | func TestUnit_Merge_AlternatePath_StringBothSet(t *testing.T) { 59 | a := "bar" 60 | b := "foo" 61 | expected := "foo" 62 | ret := Merge(a, b) 63 | 64 | if !reflect.DeepEqual(ret, expected) { 65 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 66 | } 67 | } 68 | 69 | func TestUnit_Merge_AlternatePath_IntEmptyA(t *testing.T) { 70 | a := 0 71 | b := 10 72 | expected := 10 73 | ret := Merge(a, b) 74 | 75 | if !reflect.DeepEqual(ret, expected) { 76 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 77 | } 78 | } 79 | 80 | func TestUnit_Merge_AlternatePath_IntEmptyB(t *testing.T) { 81 | a := 10 82 | b := 0 83 | expected := 10 84 | ret := Merge(a, b) 85 | 86 | if !reflect.DeepEqual(ret, expected) { 87 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 88 | } 89 | } 90 | 91 | func TestUnit_Merge_AlternatePath_IntNotSetA(t *testing.T) { 92 | var a int 93 | b := 10 94 | expected := 10 95 | ret := Merge(a, b) 96 | 97 | if !reflect.DeepEqual(ret, expected) { 98 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 99 | } 100 | } 101 | 102 | func TestUnit_Merge_AlternatePath_IntNotSetB(t *testing.T) { 103 | a := 10 104 | var b int 105 | expected := 10 106 | ret := Merge(a, b) 107 | 108 | if !reflect.DeepEqual(ret, expected) { 109 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 110 | } 111 | } 112 | 113 | func TestUnit_Merge_AlternatePath_IntBothSet(t *testing.T) { 114 | a := 5 115 | b := 10 116 | expected := 10 117 | ret := Merge(a, b) 118 | 119 | if !reflect.DeepEqual(ret, expected) { 120 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 121 | } 122 | } 123 | 124 | func TestUnit_Merge_AlternatePath_UIntEmptyA(t *testing.T) { 125 | a := uint64(0) 126 | b := uint64(10) 127 | expected := uint64(10) 128 | ret := Merge(a, b) 129 | 130 | if !reflect.DeepEqual(ret, expected) { 131 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 132 | } 133 | } 134 | 135 | func TestUnit_Merge_AlternatePath_UIntEmptyB(t *testing.T) { 136 | a := uint64(10) 137 | b := uint64(0) 138 | expected := uint64(10) 139 | ret := Merge(a, b) 140 | 141 | if !reflect.DeepEqual(ret, expected) { 142 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 143 | } 144 | } 145 | 146 | func TestUnit_Merge_AlternatePath_UIntNotSetA(t *testing.T) { 147 | var a uint64 148 | b := uint64(10) 149 | expected := uint64(10) 150 | ret := Merge(a, b) 151 | 152 | if !reflect.DeepEqual(ret, expected) { 153 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 154 | } 155 | } 156 | 157 | func TestUnit_Merge_AlternatePath_UIntNotSetB(t *testing.T) { 158 | a := uint64(10) 159 | var b uint64 160 | expected := uint64(10) 161 | ret := Merge(a, b) 162 | 163 | if !reflect.DeepEqual(ret, expected) { 164 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 165 | } 166 | } 167 | 168 | func TestUnit_Merge_AlternatePath_UIntBothSet(t *testing.T) { 169 | a := uint64(5) 170 | b := uint64(10) 171 | expected := uint64(10) 172 | ret := Merge(a, b) 173 | 174 | if !reflect.DeepEqual(ret, expected) { 175 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 176 | } 177 | } 178 | 179 | func TestUnit_Merge_AlternatePath_FloatEmptyA(t *testing.T) { 180 | a := float64(0) 181 | b := float64(10) 182 | expected := float64(10) 183 | ret := Merge(a, b) 184 | 185 | if !reflect.DeepEqual(ret, expected) { 186 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 187 | } 188 | } 189 | 190 | func TestUnit_Merge_AlternatePath_FloatEmptyB(t *testing.T) { 191 | a := float64(10) 192 | b := float64(0) 193 | expected := float64(10) 194 | ret := Merge(a, b) 195 | 196 | if !reflect.DeepEqual(ret, expected) { 197 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 198 | } 199 | } 200 | 201 | func TestUnit_Merge_AlternatePath_FloatNotSetA(t *testing.T) { 202 | var a float64 203 | b := float64(10) 204 | expected := float64(10) 205 | ret := Merge(a, b) 206 | 207 | if !reflect.DeepEqual(ret, expected) { 208 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 209 | } 210 | } 211 | 212 | func TestUnit_Merge_AlternatePath_FloatNotSetB(t *testing.T) { 213 | a := float64(10) 214 | var b float64 215 | expected := float64(10) 216 | ret := Merge(a, b) 217 | 218 | if !reflect.DeepEqual(ret, expected) { 219 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 220 | } 221 | } 222 | 223 | func TestUnit_Merge_AlternatePath_FloatBothSet(t *testing.T) { 224 | a := float64(5) 225 | b := float64(10) 226 | expected := float64(10) 227 | ret := Merge(a, b) 228 | 229 | if !reflect.DeepEqual(ret, expected) { 230 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 231 | } 232 | } 233 | 234 | func TestUnit_Merge_AlternatePath_BoolNotSetA(t *testing.T) { 235 | var a bool 236 | b := true 237 | expected := true 238 | ret := Merge(a, b) 239 | 240 | if !reflect.DeepEqual(ret, expected) { 241 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 242 | } 243 | } 244 | 245 | func TestUnit_Merge_AlternatePath_BoolNotSetB(t *testing.T) { 246 | a := true 247 | var b bool 248 | expected := true 249 | ret := Merge(a, b) 250 | 251 | if !reflect.DeepEqual(ret, expected) { 252 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 253 | } 254 | } 255 | 256 | // Note that because an empty bool is the same as false, true always wins out 257 | func TestUnit_Merge_AlternatePath_BoolBothSet(t *testing.T) { 258 | a := true 259 | b := false 260 | expected := true 261 | ret := Merge(a, b) 262 | 263 | if !reflect.DeepEqual(ret, expected) { 264 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 265 | } 266 | } 267 | 268 | func TestUnit_Merge_AlternatePath_ArrayEmptyA(t *testing.T) { 269 | a := []int{} 270 | b := []int{10, 11, 12} 271 | expected := []int{10, 11, 12} 272 | ret := Merge(a, b) 273 | 274 | if !reflect.DeepEqual(ret, expected) { 275 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 276 | } 277 | } 278 | 279 | func TestUnit_Merge_AlternatePath_ArrayEmptyB(t *testing.T) { 280 | a := []int{10, 11, 12} 281 | b := []int{} 282 | expected := []int{10, 11, 12} 283 | ret := Merge(a, b) 284 | 285 | if !reflect.DeepEqual(ret, expected) { 286 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 287 | } 288 | } 289 | 290 | func TestUnit_Merge_AlternatePath_ArrayNotSetA(t *testing.T) { 291 | var a []int 292 | b := []int{10, 11, 12} 293 | expected := []int{10, 11, 12} 294 | ret := Merge(a, b) 295 | 296 | if !reflect.DeepEqual(ret, expected) { 297 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 298 | } 299 | } 300 | 301 | func TestUnit_Merge_AlternatePath_ArrayNotSetB(t *testing.T) { 302 | a := []int{10, 11, 12} 303 | var b []int 304 | expected := []int{10, 11, 12} 305 | ret := Merge(a, b) 306 | 307 | if !reflect.DeepEqual(ret, expected) { 308 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 309 | } 310 | } 311 | 312 | func TestUnit_Merge_AlternatePath_ArrayBothSet(t *testing.T) { 313 | a := []int{5, 6, 7} 314 | b := []int{10, 11, 12} 315 | expected := []int{10, 11, 12} 316 | ret := Merge(a, b) 317 | 318 | if !reflect.DeepEqual(ret, expected) { 319 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 320 | } 321 | } 322 | 323 | func TestUnit_Merge_AlternatePath_Pointers(t *testing.T) { 324 | a := 10 325 | b := 5 326 | expected := &b 327 | ret := Merge(&a, &b) 328 | 329 | if !reflect.DeepEqual(ret, expected) { 330 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 331 | } 332 | } 333 | 334 | func TestUnit_Merge_AlternatePath_Map(t *testing.T) { 335 | baseData := make(map[string]string) 336 | baseData["test"] = "foo" 337 | baseData["test2"] = "bar" 338 | 339 | overrideData := make(map[string]string) 340 | overrideData["test2"] = "override" 341 | 342 | expected := make(map[string]string) 343 | expected["test"] = "foo" 344 | expected["test2"] = "override" 345 | 346 | ret := merge.Merge(baseData, overrideData) 347 | 348 | if !reflect.DeepEqual(ret, expected) { 349 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 350 | } 351 | } 352 | 353 | func TestUnit_Merge_AlternatePath_MapEmptyValue(t *testing.T) { 354 | baseData := make(map[string]string) 355 | baseData["test"] = "foo" 356 | baseData["test2"] = "" 357 | 358 | overrideData := make(map[string]string) 359 | overrideData["test3"] = "" 360 | 361 | expected := make(map[string]string) 362 | expected["test"] = "foo" 363 | expected["test2"] = "" 364 | expected["test3"] = "" 365 | 366 | ret := merge.Merge(baseData, overrideData) 367 | 368 | if !reflect.DeepEqual(ret, expected) { 369 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 370 | } 371 | } 372 | 373 | func TestUnit_Merge_AlternatePath_MapEmptyValuePtr(t *testing.T) { 374 | baseData := make(map[string]*string) 375 | baseData["test"] = makeStringPtr("foo") 376 | baseData["test2"] = makeStringPtr("") 377 | 378 | overrideData := make(map[string]*string) 379 | overrideData["test3"] = makeStringPtr("") 380 | 381 | expected := make(map[string]*string) 382 | expected["test"] = makeStringPtr("foo") 383 | expected["test2"] = makeStringPtr("") 384 | expected["test3"] = makeStringPtr("") 385 | 386 | ret := merge.Merge(baseData, overrideData) 387 | 388 | if !reflect.DeepEqual(ret, expected) { 389 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 390 | } 391 | } 392 | 393 | func TestUnit_Merge_AlternatePath_MapInterface(t *testing.T) { 394 | base := make(map[string]map[string]interface{}) 395 | base["Config"] = make(map[string]interface{}) 396 | base["Config"]["Test"] = "this is a test value" 397 | base["Config"]["Test2"] = "this is also a test value" 398 | 399 | override := make(map[string]map[string]interface{}) 400 | override["Config"] = make(map[string]interface{}) 401 | override["Config"]["foo"] = "bar" 402 | override["Config"]["Test2"] = "baz" 403 | 404 | expected := make(map[string]map[string]interface{}) 405 | expected["Config"] = make(map[string]interface{}) 406 | expected["Config"]["foo"] = "bar" 407 | expected["Config"]["Test"] = "this is a test value" 408 | expected["Config"]["Test2"] = "baz" 409 | 410 | ret := merge.Merge(base, override) 411 | 412 | if !reflect.DeepEqual(ret, expected) { 413 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 414 | } 415 | } 416 | 417 | func TestUnit_Merge_AlternatePath_MapInterfaceEmptyValue(t *testing.T) { 418 | base := make(map[string]map[string]interface{}) 419 | base["Config"] = make(map[string]interface{}) 420 | base["Config"]["Test"] = "this is a test value" 421 | base["Config"]["Test2"] = "this is also a test value" 422 | 423 | override := make(map[string]map[string]interface{}) 424 | override["Config"] = make(map[string]interface{}) 425 | override["Config"]["foo"] = "bar" 426 | override["Config"]["Test2"] = "baz" 427 | override["Config"]["Test3"] = nil 428 | 429 | expected := make(map[string]map[string]interface{}) 430 | expected["Config"] = make(map[string]interface{}) 431 | expected["Config"]["foo"] = "bar" 432 | expected["Config"]["Test"] = "this is a test value" 433 | expected["Config"]["Test2"] = "baz" 434 | expected["Config"]["Test3"] = nil 435 | 436 | ret := merge.Merge(base, override) 437 | 438 | if !reflect.DeepEqual(ret, expected) { 439 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 440 | } 441 | } 442 | 443 | func TestUnit_Merge_AlternatePath_StructMapStruct(t *testing.T) { 444 | type testStruct struct { 445 | Foo string 446 | Bar string 447 | } 448 | 449 | type mStruct struct { 450 | M map[string]testStruct 451 | } 452 | 453 | b := make(map[string]testStruct) 454 | o := make(map[string]testStruct) 455 | e := make(map[string]testStruct) 456 | 457 | base := testStruct{} 458 | base.Foo = "foo" 459 | base.Bar = "hello" 460 | b["test"] = base 461 | bs := mStruct{b} 462 | 463 | override := testStruct{} 464 | override.Bar = "bar" 465 | o["test"] = override 466 | os := mStruct{o} 467 | 468 | expected := testStruct{} 469 | expected.Foo = "foo" 470 | expected.Bar = "bar" 471 | e["test"] = expected 472 | es := &mStruct{e} 473 | 474 | ret := Merge(bs, os) 475 | 476 | if !reflect.DeepEqual(ret, es) { 477 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, es) 478 | } 479 | } 480 | 481 | func TestUnit_Merge_AlternatePath_StructPointer(t *testing.T) { 482 | 483 | type innerStruct struct { 484 | InnerVal string 485 | } 486 | type testStruct struct { 487 | Foo string 488 | Bar *innerStruct 489 | } 490 | 491 | i1 := &innerStruct{"inner"} 492 | base := testStruct{"bar", i1} 493 | 494 | i2 := &innerStruct{"inner2"} 495 | override := testStruct{"", i2} 496 | 497 | expected := testStruct{"bar", i2} 498 | 499 | ret := Merge(base, override) 500 | if ret.(*testStruct).Foo != expected.Foo || ret.(*testStruct).Bar.InnerVal != expected.Bar.InnerVal { 501 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 502 | } 503 | } 504 | 505 | func TestUnit_Merge_AlternatePath_StructPrivate(t *testing.T) { 506 | 507 | type innerStruct struct { 508 | InnerVal string 509 | } 510 | type testStruct struct { 511 | Foo string 512 | bar innerStruct 513 | } 514 | 515 | i1 := innerStruct{"inner"} 516 | base := testStruct{"bar", i1} 517 | 518 | i2 := innerStruct{"inner2"} 519 | override := testStruct{"", i2} 520 | 521 | expected := testStruct{"bar", innerStruct{}} 522 | 523 | ret := Merge(base, override) 524 | if ret.(*testStruct).Foo != expected.Foo || ret.(*testStruct).bar.InnerVal != expected.bar.InnerVal { 525 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 526 | } 527 | } 528 | 529 | func TestUnit_Merge_AlternatePath_StructWithStringPointers(t *testing.T) { 530 | type testStruct struct { 531 | Foo string 532 | Bar *string 533 | } 534 | 535 | bs := "bar" 536 | base := testStruct{Foo: "foo"} 537 | 538 | override := testStruct{"foo2", &bs} 539 | expected := testStruct{"foo2", &bs} 540 | 541 | ret := Merge(base, override) 542 | if ret.(*testStruct).Foo != expected.Foo || ret.(*testStruct).Bar != expected.Bar { 543 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 544 | } 545 | } 546 | 547 | func TestUnit_Merge_AlternatePath_StructWithStringNilPointers(t *testing.T) { 548 | type testStruct struct { 549 | Foo string 550 | Bar *string 551 | } 552 | 553 | bs := "bar" 554 | base := testStruct{"foo", &bs} 555 | 556 | override := testStruct{"foo2", nil} 557 | expected := testStruct{"foo2", &bs} 558 | 559 | ret := Merge(base, override) 560 | if ret.(*testStruct).Foo != expected.Foo || ret.(*testStruct).Bar != expected.Bar { 561 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 562 | } 563 | } 564 | 565 | func TestUnit_Merge_AlternatePath_StructWithStructNilPointers(t *testing.T) { 566 | 567 | type innerStruct struct { 568 | InnerVal string 569 | } 570 | type testStruct struct { 571 | Foo string 572 | Bar *innerStruct 573 | } 574 | 575 | bs := innerStruct{"bar"} 576 | base := testStruct{"foo", &bs} 577 | 578 | override := testStruct{"foo2", nil} 579 | expected := testStruct{"foo2", &bs} 580 | 581 | ret := Merge(base, override) 582 | if ret.(*testStruct).Foo != expected.Foo || ret.(*testStruct).Bar != expected.Bar { 583 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 584 | } 585 | } 586 | 587 | func TestUnit_Merge_AlternatePath_StructWithStructNilPointersBase(t *testing.T) { 588 | 589 | type innerStruct struct { 590 | InnerVal string 591 | } 592 | type testStruct struct { 593 | Foo string 594 | Bar *innerStruct 595 | } 596 | 597 | bs := innerStruct{"bar"} 598 | base := testStruct{"foo", nil} 599 | 600 | override := testStruct{"foo2", &bs} 601 | expected := testStruct{"foo2", &bs} 602 | 603 | ret := Merge(base, override) 604 | if ret.(*testStruct).Foo != expected.Foo || ret.(*testStruct).Bar != expected.Bar { 605 | t.Errorf("Actual ( %#v ) does not match expected ( %#v )", ret, expected) 606 | } 607 | } 608 | --------------------------------------------------------------------------------