├── .gitignore ├── LICENSE ├── automapper.go ├── automapper_test.go └── readme.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Peter Strøiman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /automapper.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Peter Strøiman, distributed under the MIT license 2 | 3 | // Package automapper provides support for mapping between two different types 4 | // with compatible fields. The intended application for this is when you use 5 | // one set of types to represent DTOs (data transfer objects, e.g. json data), 6 | // and a different set of types internally in the application. Using this 7 | // package can help converting from one type to another. 8 | // 9 | // This package uses reflection to perform mapping which should be fine for 10 | // all but the most demanding applications. 11 | package automapper 12 | 13 | import ( 14 | "fmt" 15 | "reflect" 16 | ) 17 | 18 | // Map fills out the fields in dest with values from source. All fields in the 19 | // destination object must exist in the source object. 20 | // 21 | // Object hierarchies with nested structs and slices are supported, as long as 22 | // type types of nested structs/slices follow the same rules, i.e. all fields 23 | // in destination structs must be found on the source struct. 24 | // 25 | // Embedded/anonymous structs are supported 26 | // 27 | // Values that are not exported/not public will not be mapped. 28 | // 29 | // It is a design decision to panic when a field cannot be mapped in the 30 | // destination to ensure that a renamed field in either the source or 31 | // destination does not result in subtle silent bug. 32 | func Map(source, dest interface{}) { 33 | var destType = reflect.TypeOf(dest) 34 | if destType.Kind() != reflect.Ptr { 35 | panic("Dest must be a pointer type") 36 | } 37 | var sourceVal = reflect.ValueOf(source) 38 | var destVal = reflect.ValueOf(dest).Elem() 39 | mapValues(sourceVal, destVal, false) 40 | } 41 | 42 | // MapLoose works just like Map, except it doesn't fail when the destination 43 | // type contains fields not supplied by the source. 44 | // 45 | // This function is meant to be a temporary solution - the general idea is 46 | // that the Map function should take a number of options that can modify its 47 | // behavior - but I'd rather not add that functionality before I have a better 48 | // idea what is a good options format. 49 | func MapLoose(source, dest interface{}) { 50 | var destType = reflect.TypeOf(dest) 51 | if destType.Kind() != reflect.Ptr { 52 | panic("Dest must be a pointer type") 53 | } 54 | var sourceVal = reflect.ValueOf(source) 55 | var destVal = reflect.ValueOf(dest).Elem() 56 | mapValues(sourceVal, destVal, true) 57 | } 58 | 59 | func mapValues(sourceVal, destVal reflect.Value, loose bool) { 60 | destType := destVal.Type() 61 | if destType.Kind() == reflect.Struct { 62 | if sourceVal.Type().Kind() == reflect.Ptr { 63 | if sourceVal.IsNil() { 64 | // If source is nil, it maps to an empty struct 65 | sourceVal = reflect.New(sourceVal.Type().Elem()) 66 | } 67 | sourceVal = sourceVal.Elem() 68 | } 69 | for i := 0; i < destVal.NumField(); i++ { 70 | mapField(sourceVal, destVal, i, loose) 71 | } 72 | } else if destType == sourceVal.Type() { 73 | destVal.Set(sourceVal) 74 | } else if destType.Kind() == reflect.Ptr { 75 | if valueIsNil(sourceVal) { 76 | return 77 | } 78 | val := reflect.New(destType.Elem()) 79 | mapValues(sourceVal, val.Elem(), loose) 80 | destVal.Set(val) 81 | } else if destType.Kind() == reflect.Slice { 82 | mapSlice(sourceVal, destVal, loose) 83 | } else { 84 | panic("Currently not supported") 85 | } 86 | } 87 | 88 | func mapSlice(sourceVal, destVal reflect.Value, loose bool) { 89 | destType := destVal.Type() 90 | length := sourceVal.Len() 91 | target := reflect.MakeSlice(destType, length, length) 92 | for j := 0; j < length; j++ { 93 | val := reflect.New(destType.Elem()).Elem() 94 | mapValues(sourceVal.Index(j), val, loose) 95 | target.Index(j).Set(val) 96 | } 97 | 98 | if length == 0 { 99 | verifyArrayTypesAreCompatible(sourceVal, destVal, loose) 100 | } 101 | destVal.Set(target) 102 | } 103 | 104 | func verifyArrayTypesAreCompatible(sourceVal, destVal reflect.Value, loose bool) { 105 | dummyDest := reflect.New(reflect.PtrTo(destVal.Type())) 106 | dummySource := reflect.MakeSlice(sourceVal.Type(), 1, 1) 107 | mapValues(dummySource, dummyDest.Elem(), loose) 108 | } 109 | 110 | func mapField(source, destVal reflect.Value, i int, loose bool) { 111 | destType := destVal.Type() 112 | fieldName := destType.Field(i).Name 113 | defer func() { 114 | if r := recover(); r != nil { 115 | panic(fmt.Sprintf("Error mapping field: %s. DestType: %v. SourceType: %v. Error: %v", fieldName, destType, source.Type(), r)) 116 | } 117 | }() 118 | 119 | destField := destVal.Field(i) 120 | if destType.Field(i).Anonymous { 121 | mapValues(source, destField, loose) 122 | } else { 123 | if valueIsContainedInNilEmbeddedType(source, fieldName) { 124 | return 125 | } 126 | sourceField := source.FieldByName(fieldName) 127 | if (sourceField == reflect.Value{}) { 128 | if loose { 129 | return 130 | } 131 | if destField.Kind() == reflect.Struct { 132 | mapValues(source, destField, loose) 133 | return 134 | } else { 135 | for i := 0; i < source.NumField(); i++ { 136 | if source.Field(i).Kind() != reflect.Struct { 137 | continue 138 | } 139 | if sourceField = source.Field(i).FieldByName(fieldName); (sourceField != reflect.Value{}) { 140 | break 141 | } 142 | } 143 | } 144 | } 145 | mapValues(sourceField, destField, loose) 146 | } 147 | } 148 | 149 | func valueIsNil(value reflect.Value) bool { 150 | return value.Type().Kind() == reflect.Ptr && value.IsNil() 151 | } 152 | 153 | func valueIsContainedInNilEmbeddedType(source reflect.Value, fieldName string) bool { 154 | structField, _ := source.Type().FieldByName(fieldName) 155 | ix := structField.Index 156 | if len(structField.Index) > 1 { 157 | parentField := source.FieldByIndex(ix[:len(ix)-1]) 158 | if valueIsNil(parentField) { 159 | return true 160 | } 161 | } 162 | return false 163 | } 164 | -------------------------------------------------------------------------------- /automapper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Peter Strøiman, distributed under the MIT license 2 | 3 | package automapper 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPanicWhenDestIsNotPointer(t *testing.T) { 12 | defer func() { recover() }() 13 | source, dest := SourceTypeA{}, DestTypeA{} 14 | Map(source, dest) 15 | 16 | t.Error("Should have panicked") 17 | } 18 | 19 | func TestDestinationIsUpdatedFromSource(t *testing.T) { 20 | source, dest := SourceTypeA{Foo: 42}, DestTypeA{} 21 | Map(source, &dest) 22 | assert.Equal(t, 42, dest.Foo) 23 | } 24 | 25 | func TestDestinationIsUpdatedFromSourceWhenSourcePassedAsPtr(t *testing.T) { 26 | source, dest := SourceTypeA{42, "Bar"}, DestTypeA{} 27 | Map(&source, &dest) 28 | assert.Equal(t, 42, dest.Foo) 29 | assert.Equal(t, "Bar", dest.Bar) 30 | } 31 | 32 | func TestWithNestedTypes(t *testing.T) { 33 | source := struct { 34 | Baz string 35 | Child SourceTypeA 36 | }{} 37 | dest := struct { 38 | Baz string 39 | Child DestTypeA 40 | }{} 41 | 42 | source.Baz = "Baz" 43 | source.Child.Bar = "Bar" 44 | Map(&source, &dest) 45 | assert.Equal(t, "Baz", dest.Baz) 46 | assert.Equal(t, "Bar", dest.Child.Bar) 47 | } 48 | 49 | func TestWithSourceSecondLevel(t *testing.T) { 50 | source := struct { 51 | Child DestTypeA 52 | }{} 53 | dest := SourceTypeA{} 54 | 55 | source.Child.Bar = "Bar" 56 | Map(&source, &dest) 57 | assert.Equal(t, "Bar", dest.Bar) 58 | } 59 | 60 | func TestWithDestSecondLevel(t *testing.T) { 61 | source := SourceTypeA{} 62 | dest := struct { 63 | Child DestTypeA 64 | }{} 65 | 66 | source.Bar = "Bar" 67 | Map(&source, &dest) 68 | assert.Equal(t, "Bar", dest.Child.Bar) 69 | } 70 | 71 | func TestWithSliceTypes(t *testing.T) { 72 | source := struct { 73 | Children []SourceTypeA 74 | }{} 75 | dest := struct { 76 | Children []DestTypeA 77 | }{} 78 | source.Children = []SourceTypeA{ 79 | SourceTypeA{Foo: 1}, 80 | SourceTypeA{Foo: 2}} 81 | 82 | Map(&source, &dest) 83 | assert.Equal(t, 1, dest.Children[0].Foo) 84 | assert.Equal(t, 2, dest.Children[1].Foo) 85 | } 86 | 87 | func TestWithMultiLevelSlices(t *testing.T) { 88 | source := struct { 89 | Parents []SourceParent 90 | }{} 91 | dest := struct { 92 | Parents []DestParent 93 | }{} 94 | source.Parents = []SourceParent{ 95 | SourceParent{ 96 | Children: []SourceTypeA{ 97 | SourceTypeA{Foo: 42}, 98 | SourceTypeA{Foo: 43}, 99 | }, 100 | }, 101 | SourceParent{ 102 | Children: []SourceTypeA{}, 103 | }, 104 | } 105 | 106 | Map(&source, &dest) 107 | } 108 | 109 | func TestWithEmptySliceAndIncompatibleTypes(t *testing.T) { 110 | defer func() { recover() }() 111 | 112 | source := struct { 113 | Children []struct{ Foo string } 114 | }{} 115 | dest := struct { 116 | Children []struct{ Bar int } 117 | }{} 118 | 119 | Map(&source, &dest) 120 | t.Error("Should have panicked") 121 | } 122 | 123 | func TestWhenSourceIsMissingField(t *testing.T) { 124 | defer func() { recover() }() 125 | source := struct { 126 | A string 127 | }{} 128 | dest := struct { 129 | A, B string 130 | }{} 131 | Map(&source, &dest) 132 | t.Error("Should have panicked") 133 | } 134 | 135 | func TestWithUnnamedFields(t *testing.T) { 136 | source := struct { 137 | Baz string 138 | SourceTypeA 139 | }{} 140 | dest := struct { 141 | Baz string 142 | DestTypeA 143 | }{} 144 | source.Baz = "Baz" 145 | source.SourceTypeA.Foo = 42 146 | 147 | Map(&source, &dest) 148 | assert.Equal(t, "Baz", dest.Baz) 149 | assert.Equal(t, 42, dest.DestTypeA.Foo) 150 | } 151 | 152 | func TestWithPointerFieldsNotNil(t *testing.T) { 153 | source := struct { 154 | Foo *SourceTypeA 155 | }{} 156 | dest := struct { 157 | Foo *DestTypeA 158 | }{} 159 | source.Foo = nil 160 | 161 | Map(&source, &dest) 162 | assert.Nil(t, dest.Foo) 163 | } 164 | 165 | func TestWithPointerFieldsNil(t *testing.T) { 166 | source := struct { 167 | Foo *SourceTypeA 168 | }{} 169 | dest := struct { 170 | Foo *DestTypeA 171 | }{} 172 | source.Foo = &SourceTypeA{Foo: 42} 173 | 174 | Map(&source, &dest) 175 | assert.NotNil(t, dest.Foo) 176 | assert.Equal(t, 42, dest.Foo.Foo) 177 | } 178 | 179 | func TestMapFromPointerToNonPointerTypeWithData(t *testing.T) { 180 | source := struct { 181 | Foo *SourceTypeA 182 | }{} 183 | dest := struct { 184 | Foo DestTypeA 185 | }{} 186 | source.Foo = &SourceTypeA{Foo: 42} 187 | 188 | Map(&source, &dest) 189 | assert.NotNil(t, dest.Foo) 190 | assert.Equal(t, 42, dest.Foo.Foo) 191 | } 192 | 193 | func TestMapFromPointerToNonPointerTypeWithoutData(t *testing.T) { 194 | source := struct { 195 | Foo *SourceTypeA 196 | }{} 197 | dest := struct { 198 | Foo DestTypeA 199 | }{} 200 | source.Foo = nil 201 | 202 | Map(&source, &dest) 203 | assert.NotNil(t, dest.Foo) 204 | assert.Equal(t, 0, dest.Foo.Foo) 205 | } 206 | 207 | func TestMapFromPointerToAnonymousTypeToFieldName(t *testing.T) { 208 | source := struct { 209 | *SourceTypeA 210 | }{} 211 | dest := struct { 212 | Foo int 213 | }{} 214 | source.SourceTypeA = nil 215 | 216 | Map(&source, &dest) 217 | assert.Equal(t, 0, dest.Foo) 218 | } 219 | 220 | func TestMapFromPointerToNonPointerTypeWithoutDataAndIncompatibleType(t *testing.T) { 221 | defer func() { recover() }() 222 | // Just make sure we stil panic 223 | source := struct { 224 | Foo *SourceTypeA 225 | }{} 226 | dest := struct { 227 | Foo struct { 228 | Baz string 229 | } 230 | }{} 231 | source.Foo = nil 232 | 233 | Map(&source, &dest) 234 | t.Error("Should have panicked") 235 | } 236 | 237 | func TestWhenUsingIncompatibleTypes(t *testing.T) { 238 | defer func() { recover() }() 239 | source := struct{ Foo string }{} 240 | dest := struct{ Foo int }{} 241 | Map(&source, &dest) 242 | t.Error("Should have panicked") 243 | } 244 | 245 | func TestWithLooseOption(t *testing.T) { 246 | source := struct { 247 | Foo string 248 | Baz int 249 | }{"Foo", 42} 250 | dest := struct { 251 | Foo string 252 | Bar int 253 | }{} 254 | MapLoose(&source, &dest) 255 | assert.Equal(t, dest.Foo, "Foo") 256 | assert.Equal(t, dest.Bar, 0) 257 | } 258 | 259 | type SourceParent struct { 260 | Children []SourceTypeA 261 | } 262 | 263 | type DestParent struct { 264 | Children []DestTypeA 265 | } 266 | 267 | type SourceTypeA struct { 268 | Foo int 269 | Bar string 270 | } 271 | 272 | type DestTypeA struct { 273 | Foo int 274 | Bar string 275 | } 276 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | # go-automapper 2 | 3 | [![GoDoc](https://godoc.org/github.com/PeteProgrammer/go-automapper?status.svg)](https://godoc.org/github.com/PeteProgrammer/go-automapper) 4 | 5 | Go-automapper can automatically map data between different types with 6 | identically named fields in GO. Useful for initializing DTO types from build in 7 | data. 8 | --------------------------------------------------------------------------------