-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanimagi.go
130 lines (111 loc) · 3.45 KB
/
animagi.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package animagi
import (
"errors"
"reflect"
)
const (
dstError = "dst must be settable"
unsupportedTransformation = "could not transform to dst"
)
type typeDescription struct {
FieldType reflect.Type
FieldValue reflect.Value
}
/*
Transform will map the data from src into
dst by calculating the fields most similar
counterpart and copying the values over.
If src and dst are of the same type then
Transform basically does a copy.
dst must be settable or an error will be returned
*/
func Transform(src, dst interface{}) (err error) {
if cannotModifyField(dst) {
return errors.New(dstError)
}
valueOfSrc := findValueOf(src)
valueOfDst := findValueOf(dst)
if valueOfSrc.Kind() == valueOfDst.Kind() {
switch valueOfDst.Kind() {
case reflect.Struct:
srcDescription := describeStructure(src)
mapToDestination("", dst, srcDescription)
default:
setValueOfDst(valueOfDst, valueOfSrc)
}
} else {
err = errors.New(unsupportedTransformation)
}
return err
}
func describeStructure(structure interface{}) map[string]typeDescription {
structureDescription := make(map[string]typeDescription)
structureValue := findValueOf(structure)
for i := 0; i < structureValue.NumField(); i++ {
field := structureValue.Field(i)
fieldName := structureValue.Type().Field(i).Name
switch reflect.Indirect(field).Kind() {
case reflect.Struct:
subDescription := describeStructure(field)
for k, v := range subDescription {
structureDescription[fieldName+"."+k] = v
}
default:
structureDescription[fieldName] = typeDescription{field.Type(), findValueOf(field)}
}
}
return structureDescription
}
func mapToDestination(currentLevel string, dst interface{}, srcDescription map[string]typeDescription) {
dstValue := findValueOf(dst)
for i := 0; i < dstValue.NumField(); i++ {
field := dstValue.Field(i)
fieldName := dstValue.Type().Field(i).Name
fullPathName := appendFieldName(currentLevel, fieldName)
if field.IsValid() && field.CanSet() {
switch field.Kind() {
case reflect.Struct:
mapToDestination(fullPathName, field, srcDescription)
case reflect.Ptr:
if val, found := findMostSimlilarSource(fullPathName, srcDescription); found {
field.Set(reflect.New(reflect.TypeOf(field.Interface()).Elem()))
setValueOfDst(field.Elem(), val.FieldValue)
}
default:
if val, found := findMostSimlilarSource(fullPathName, srcDescription); found {
setValueOfDst(field, val.FieldValue)
}
}
}
}
}
func findMostSimlilarSource(fullPathName string, srcDescription map[string]typeDescription) (typeDescription, bool) {
val, ok := srcDescription[fullPathName]
return val, ok
}
func setValueOfDst(dst, src reflect.Value) {
if dst.Type() == reflect.Indirect(src).Type() {
dst.Set(reflect.Indirect(src))
} else if reflect.Indirect(src).Type().ConvertibleTo(reflect.Indirect(dst).Type()) {
dst.Set(reflect.Indirect(src).Convert(reflect.Indirect(dst).Type()))
}
}
func findValueOf(val interface{}) (valueOf reflect.Value) {
if reflect.TypeOf(val) != reflect.TypeOf(valueOf) {
valueOf = reflect.Indirect(reflect.ValueOf(val))
} else {
valueOf = val.(reflect.Value)
}
return valueOf
}
func appendFieldName(prefix, fieldName string) (fullName string) {
if len(prefix) != 0 {
fullName = prefix + "." + fieldName
} else {
fullName = fieldName
}
return fullName
}
func cannotModifyField(field interface{}) bool {
return reflect.ValueOf(field).Kind() != reflect.Ptr || !reflect.ValueOf(field).Elem().CanSet()
}