-
Notifications
You must be signed in to change notification settings - Fork 54
/
convert.go
133 lines (124 loc) · 3.74 KB
/
convert.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
132
133
package evaluator
import (
"fmt"
"reflect"
"strconv"
"time"
)
// A TypeError is an error during type conversion.
type TypeError string
func (e TypeError) Error() string { return string(e) }
func typeErrorf(format string, a ...interface{}) TypeError {
return TypeError(fmt.Sprintf(format, a...))
}
var timeType = reflect.TypeOf(time.Now())
func conversionError(modifier string, value interface{}, typ reflect.Type) error {
if modifier != "" {
modifier += " "
}
switch ref := value.(type) {
case reflect.Value:
value = ref.Interface()
}
return typeErrorf("can't convert %s%T(%v) to type %s", modifier, value, value, typ)
}
// Convert value to the type. This is a more aggressive conversion, that will
// recursively create new map and slice values as necessary. It doesn't
// handle circular references.
func Convert(value interface{}, typ reflect.Type) (interface{}, error) { // nolint: gocyclo
value = ToLiquid(value)
r := reflect.ValueOf(value)
// int.Convert(string) returns "\x01" not "1", so guard against that in the following test
if typ.Kind() != reflect.String && value != nil && r.Type().ConvertibleTo(typ) {
return r.Convert(typ).Interface(), nil
}
if typ == timeType && r.Kind() == reflect.String {
return ParseDate(value.(string))
}
// currently unused:
// case reflect.PtrTo(r.Type()) == typ:
// return &value, nil
// }
switch typ.Kind() {
case reflect.Bool:
return !(value == nil || value == false), nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch value := value.(type) {
case bool:
if value {
return 1, nil
}
return 0, nil
case string:
return strconv.Atoi(value)
}
case reflect.Float32, reflect.Float64:
switch value := value.(type) {
// case int is handled by r.Convert(type) above
case string:
return strconv.ParseFloat(value, 64)
}
case reflect.Map:
out := reflect.MakeMap(typ)
for _, key := range r.MapKeys() {
if typ.Key().Kind() == reflect.String {
key = reflect.ValueOf(fmt.Sprint(key))
}
if !key.Type().ConvertibleTo(typ.Key()) {
return nil, conversionError("map key", key, typ.Key())
}
key = key.Convert(typ.Key())
value := r.MapIndex(key)
if typ.Elem().Kind() == reflect.String {
value = reflect.ValueOf(fmt.Sprint(value))
}
if !value.Type().ConvertibleTo(typ.Elem()) {
return nil, conversionError("map value", value, typ.Elem())
}
out.SetMapIndex(key, value.Convert(typ.Elem()))
}
return out.Interface(), nil
case reflect.Slice:
switch r.Kind() {
case reflect.Array, reflect.Slice:
out := reflect.MakeSlice(typ, 0, r.Len())
for i := 0; i < r.Len(); i++ {
item, err := Convert(r.Index(i).Interface(), typ.Elem())
if err != nil {
return nil, err
}
out = reflect.Append(out, reflect.ValueOf(item))
}
return out.Interface(), nil
case reflect.Map:
out := reflect.MakeSlice(typ, 0, r.Len())
for _, key := range r.MapKeys() {
item, err := Convert(r.MapIndex(key).Interface(), typ.Elem())
if err != nil {
return nil, err
}
out = reflect.Append(out, reflect.ValueOf(item))
}
return out.Interface(), nil
}
case reflect.String:
return fmt.Sprint(value), nil
}
return nil, conversionError("", value, typ)
}
// MustConvert is like Convert, but panics if conversion fails.
func MustConvert(value interface{}, t reflect.Type) interface{} {
out, err := Convert(value, t)
if err != nil {
panic(err)
}
return out
}
// MustConvertItem converts item to conform to the type array's element, else panics.
func MustConvertItem(item interface{}, array []interface{}) interface{} {
item, err := Convert(item, reflect.TypeOf(array).Elem())
if err != nil {
panic(typeErrorf("can't convert %#v to %s: %s", item, reflect.TypeOf(array).Elem(), err))
}
return item
}