-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdto.go
127 lines (108 loc) · 3.17 KB
/
dto.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
package dto
import (
"errors"
"fmt"
"reflect"
"strings"
)
var (
dtoFieldTag = "dto"
)
// Change default tag `dto` to custom
func SetFieldTag(tag string) {
if tag == "" || strings.Trim(tag, " ") == "" {
panic("empty tag name")
}
dtoFieldTag = tag
}
func RequestToDTO(dst interface{}, src ...interface{}) error {
dstRv := reflect.ValueOf(dst)
if dstRv.Kind() != reflect.Ptr || dstRv.IsNil() {
return errors.New(fmt.Sprintf("cannot convert to dto: %s", reflect.TypeOf(dst)))
}
if len(src) > 0 {
for index, val := range src {
srcRv := reflect.ValueOf(val)
switch srcRv.Kind() {
case reflect.Struct:
err := parseStruct(dstRv, srcRv)
if err != nil {
return err
}
case reflect.Ptr, reflect.UnsafePointer:
if srcRv.IsNil() {
return errors.New(fmt.Sprintf("cannot convert source to dto: %d argument is nil", index))
}
err := parseStruct(dstRv, srcRv.Elem())
if err != nil {
return err
}
default:
return errors.New(fmt.Sprintf("cannot convert source to dto: %s", srcRv.Type()))
}
}
}
return nil
}
func parseStruct(dst reflect.Value, srcVal reflect.Value) error {
if srcVal.Type().Kind() != reflect.Struct {
return errors.New(fmt.Sprintf("unsupported type for dest argument: %s", srcVal.Type().Key()))
}
srcType := srcVal.Type()
fieldsCount := srcVal.NumField()
for i := 0; i < fieldsCount; i++ {
fieldVal := srcVal.Field(i)
for fieldVal.Kind() == reflect.Ptr && !fieldVal.IsNil() {
fieldVal = fieldVal.Elem()
}
tag := srcType.Field(i).Tag.Get(dtoFieldTag)
var targetField reflect.Value
if tag == "-" {
continue
} else if tag == "" {
targetField = dst.Elem().FieldByName(srcType.Field(i).Name)
if !targetField.IsValid() {
return errors.New(fmt.Sprintf("field not found by name and empty dto tag \"%s\" value for: %s", dtoFieldTag, srcType.Field(i).Name))
}
} else {
// Looking "tag_field_name", converted to "TagFieldName"
// for public field in target structure
targetField = dst.Elem().FieldByName(tagValueToFieldName(tag))
// Looking for tag value in target structure
if !targetField.IsValid() {
targetField = findFieldByTagValue(dst.Elem(), tag)
}
}
if !targetField.IsValid() {
return errors.New(fmt.Sprintf("not found field with tag: %s", tag))
}
if !targetField.CanSet() {
return errors.New(fmt.Sprintf("not writable field: %s", tag))
}
if targetField.Type() != srcType.Field(i).Type {
return errors.New(fmt.Sprintf("incompatible types source %s and target %s", srcType.Field(i).Type, targetField.Type()))
}
targetField.Set(fieldVal)
}
return nil
}
// Converts tag field value to capitalized field name
// "fieldname" => "Fieldname"
// "field_name" => "FieldName"
func tagValueToFieldName(src string) string {
src = strings.Replace(src, "_", " ", -1)
return strings.Replace(strings.Title(src), " ", "", -1)
}
func findFieldByTagValue(dst reflect.Value, tag string) reflect.Value {
if dst.Kind() == reflect.Ptr {
panic("dst cannot be pointer")
}
fieldsCount := dst.NumField()
fieldType := dst.Type()
for i := 0; i < fieldsCount; i++ {
if fieldType.Field(i).Tag.Get(dtoFieldTag) == tag {
return dst.Field(i)
}
}
return reflect.Value{}
}