-
Notifications
You must be signed in to change notification settings - Fork 2
/
fields.go
131 lines (112 loc) · 3.13 KB
/
fields.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
131
package sol
import (
"reflect"
"time"
"database/sql"
)
const (
tagLabel = "db"
ignoreTag = "-"
)
var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
// Field holds value and type info on a struct field
type Field struct {
Value reflect.Value
Type reflect.StructField
Name string
Options options
}
// Exists returns true if the field exists
func (field Field) Exists() bool {
return field.Name != ""
}
// IsIgnorable returns true if the field can be ignored
func (field Field) IsIgnorable() bool {
return field.Name == ignoreTag
}
// IsOmittable returns true if the field can be omitted
func (field Field) IsOmittable() bool {
return field.Options.Has(OmitEmpty) && isEmptyValue(field.Value)
}
// NewField creates a Field from a reflect.Value and Type
func NewField(val reflect.Value, typ reflect.StructField, index ...int) (field Field) {
field.Value = val
field.Type = typ
field.Name, field.Options = parseTag(typ.Tag.Get(tagLabel))
if field.Name == "" {
field.Name = field.Type.Name // Fallback to struct field name
}
// Prepend the given index values to the type index
field.Type.Index = append(index, field.Type.Index...)
return
}
// DeepFields returns value and type info on struct types. It will return
// nothing if the given object is not a struct or *struct type.
func DeepFields(obj interface{}, index ...int) (fields []Field) {
val := reflect.ValueOf(obj)
typ := reflect.TypeOf(obj)
if typ != nil && typ.Kind() == reflect.Ptr {
typ = val.Elem().Type()
val = reflect.Indirect(val)
}
if typ == nil || typ.Kind() != reflect.Struct {
return // Do not iterate over non-struct types
}
for i := 0; i < typ.NumField(); i++ {
field := NewField(val.Field(i), typ.Field(i), index...)
// If the field has an ignore tag, skip it and any descendants
if field.IsIgnorable() {
continue
}
// Skip fields that cannot be interfaced
if !field.Value.CanInterface() {
continue
}
// Time structs have special handling
switch field.Value.Interface().(type) {
case time.Time, *time.Time:
fields = append(fields, field)
continue
}
// Scanners have special handling
if reflect.PtrTo(field.Type.Type).Implements(scannerType) {
fields = append(fields, field)
continue
}
// Save the field or recurse further
switch field.Value.Kind() {
case reflect.Struct:
fields = append(fields, DeepFields(
field.Value.Interface(),
field.Type.Index...,
)...)
default:
fields = append(fields, field)
}
}
return
}
// AlignFields will reorder the given fields array to match the columns.
// Columns that do not match fields will be given empty field structs.
func AlignFields(columns []string, fields []Field) []Field {
out := make([]Field, len(columns))
for i, column := range columns {
for _, field := range fields {
// Match names either exactly and using camel to snake
if field.Name == column || camelToSnake(field.Name) == column {
out[i] = field
break
}
}
}
return out
}
// NoMatchingFields returns true if no fields exist
func NoMatchingFields(fields []Field) bool {
for _, field := range fields {
if field.Exists() {
return false
}
}
return true
}