Skip to content

Commit

Permalink
add Slice()/SliceE()
Browse files Browse the repository at this point in the history
  • Loading branch information
shockerli committed Mar 10, 2021
1 parent 6d56ab3 commit a8ada29
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 28 deletions.
13 changes: 13 additions & 0 deletions cvt.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,16 @@ func String(v interface{}, def ...string) string {

return ""
}

// Slice convert an interface to a []interface{} type, with default value
func Slice(v interface{}, def ...[]interface{}) []interface{} {
if v, err := SliceE(v); err == nil {
return v
}

if len(def) > 0 {
return def[0]
}

return nil
}
42 changes: 42 additions & 0 deletions cvt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1306,3 +1306,45 @@ func TestString_BaseLine(t *testing.T) {
assert.Equal(t, tt.expect, v, msg)
}
}

func TestSlice_HasDefault(t *testing.T) {
tests := []struct {
input interface{}
def []interface{}
expect []interface{}
}{
// supported value, def is not used, def != expect
{[]int{1, 2, 3}, []interface{}{"a", "b"}, []interface{}{1, 2, 3}},
{testing.T{}, []interface{}{1, 2, 3}, nil},

// unsupported value, def == expect
{int(123), []interface{}{"hello"}, []interface{}{"hello"}},
{uint16(123), nil, nil},
{func() {}, []interface{}{}, []interface{}{}},
}

for i, tt := range tests {
msg := fmt.Sprintf("i = %d, input[%+v], def[%+v], expect[%+v]", i, tt.input, tt.def, tt.expect)

v := cvt.Slice(tt.input, tt.def)
assert.Equal(t, tt.expect, v, msg)
}
}

func TestSlice_BaseLine(t *testing.T) {
tests := []struct {
input interface{}
expect []interface{}
}{
{int(123), nil},
{uint16(123), nil},
{func() {}, nil},
}

for i, tt := range tests {
msg := fmt.Sprintf("i = %d, input[%+v], expect[%+v]", i, tt.input, tt.expect)

v := cvt.Slice(tt.input)
assert.Equal(t, tt.expect, v, msg)
}
}
104 changes: 76 additions & 28 deletions cvte.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"math"
"reflect"
"sort"
"strconv"
"strings"
)
Expand Down Expand Up @@ -36,7 +37,7 @@ func BoolE(val interface{}) (bool, error) {
return false, nil
}

switch rk {
switch rk.Kind() {
// by elem length
case reflect.Array, reflect.Slice, reflect.Map:
return rv.Len() > 0, nil
Expand Down Expand Up @@ -354,58 +355,105 @@ func StringE(val interface{}) (string, error) {
return "", newErr(val, "string")
}

// SliceE convert an interface to a []interface{} type
func SliceE(val interface{}) (sl []interface{}, err error) {
if val == nil {
return
}

_, rt, rv := Indirect(val)

switch rt.Kind() {
case reflect.String:
for _, vvv := range rv.String() {
sl = append(sl, vvv)
}
return
case reflect.Slice, reflect.Array:
for j := 0; j < rv.Len(); j++ {
sl = append(sl, rv.Index(j).Interface())
}
return
case reflect.Map:
var keys = rv.MapKeys()
// sorted by keys
sort.Slice(keys, func(i, j int) bool {
return strings.Compare(String(keys[i].Interface()), String(keys[j].Interface())) < 0
})
for _, key := range keys {
sl = append(sl, rv.MapIndex(key).Interface())
}
return
case reflect.Struct:
sl = deepStructValues(rt, rv)
return
}

return nil, newErr(val, "slice")
}

func deepStructValues(rt reflect.Type, rv reflect.Value) (sl []interface{}) {
for j := 0; j < rv.NumField(); j++ {
if rt.Field(j).Anonymous {
sl = append(sl, deepStructValues(rt.Field(j).Type, rv.Field(j))...)
} else if rv.Field(j).CanInterface() {
sl = append(sl, rv.Field(j).Interface())
}
}
return
}

// Indirect returns the value with base type
func Indirect(a interface{}) (val interface{}, k reflect.Kind, v reflect.Value) {
func Indirect(a interface{}) (val interface{}, rt reflect.Type, rv reflect.Value) {
if a == nil {
return
}

t := reflect.TypeOf(a)
v = reflect.ValueOf(a)
k = t.Kind()
rt = reflect.TypeOf(a)
rv = reflect.ValueOf(a)

switch t.Kind() {
case reflect.Ptr:
for v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
switch rt.Kind() {
case reflect.Ptr: // indirect the base type, if is be referenced many times
for rv.Kind() == reflect.Ptr && !rv.IsNil() {
rv = rv.Elem()
}
return Indirect(v.Interface())
return Indirect(rv.Interface())
case reflect.Bool:
val = v.Bool()
val = rv.Bool()
case reflect.Int:
val = int(v.Int())
val = int(rv.Int())
case reflect.Int8:
val = int8(v.Int())
val = int8(rv.Int())
case reflect.Int16:
val = int16(v.Int())
val = int16(rv.Int())
case reflect.Int32:
val = int32(v.Int())
val = int32(rv.Int())
case reflect.Int64:
val = v.Int()
val = rv.Int()
case reflect.Uint:
val = uint(v.Uint())
val = uint(rv.Uint())
case reflect.Uint8:
val = uint8(v.Uint())
val = uint8(rv.Uint())
case reflect.Uint16:
val = uint16(v.Uint())
val = uint16(rv.Uint())
case reflect.Uint32:
val = uint32(v.Uint())
val = uint32(rv.Uint())
case reflect.Uint64:
val = v.Uint()
val = rv.Uint()
case reflect.Uintptr:
val = uintptr(v.Uint())
val = uintptr(rv.Uint())
case reflect.Float32:
val = float32(v.Float())
val = float32(rv.Float())
case reflect.Float64:
val = v.Float()
val = rv.Float()
case reflect.Complex64:
val = complex64(v.Complex())
val = complex64(rv.Complex())
case reflect.Complex128:
val = v.Complex()
val = rv.Complex()
case reflect.String:
val = v.String()
val = rv.String()
default:
val = v.Interface()
val = rv.Interface()
}

return
Expand Down
87 changes: 87 additions & 0 deletions cvte_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,31 @@ func (TestMarshalJSON) MarshalJSON() ([]byte, error) {
return []byte("MarshalJSON"), nil
}

type TestStructA struct {
A1 int
TestStructB
A2 string
DD TestStructD
}

type TestStructB struct {
TestStructC
B1 int
}

type TestStructC struct {
C1 string
}

type TestStructD struct {
D1 int
}

type TestStructE struct {
D1 int
DD *TestStructD
}

func TestBoolE(t *testing.T) {
tests := []struct {
input interface{}
Expand Down Expand Up @@ -1302,3 +1327,65 @@ func TestStringE(t *testing.T) {
assert.Equal(t, tt.expect, v, msg)
}
}

func TestSliceE(t *testing.T) {
tests := []struct {
input interface{}
expect []interface{}
isErr bool
}{
{"hello", []interface{}{'h', 'e', 'l', 'l', 'o'}, false},
{[]byte("hey"), []interface{}{byte('h'), byte('e'), byte('y')}, false},
{[]rune("我爱中国"), []interface{}{'我', '爱', '中', '国'}, false},
{[]int{}, nil, false},
{[]int{1, 2, 3}, []interface{}{1, 2, 3}, false},
{[]string{}, nil, false},
{[]string{"a", "b", "c"}, []interface{}{"a", "b", "c"}, false},
{[]interface{}{1, "a", -1, nil}, []interface{}{1, "a", -1, nil}, false},
{[...]string{}, nil, false},
{[...]string{"a", "b", "c"}, []interface{}{"a", "b", "c"}, false},
{map[int]string{}, nil, false},
{map[int]string{1: "111", 2: "222"}, []interface{}{"111", "222"}, false},
{map[int]TestStructC{}, nil, false},
{map[int]TestStructC{1: {"c1"}, 2: {"c2"}}, []interface{}{TestStructC{"c1"}, TestStructC{"c2"}}, false},
// map key convert to string, and sorted by key asc
{map[interface{}]string{
"k": "k",
1: "1",
0: "0",
"b": "b",
-1: "-1",
"3c": "3c",
-0.1: "-0.1",
}, []interface{}{"-0.1", "-1", "0", "1", "3c", "b", "k"}, false},

{testing.T{}, nil, false},
{&testing.T{}, nil, false},
{TestStructA{}, []interface{}{0, "", 0, "", TestStructD{0}}, false},
{&TestStructB{}, []interface{}{"", 0}, false},
{&TestStructE{}, []interface{}{0, (*TestStructD)(nil)}, false},

// errors
{int(123), nil, true},
{uint16(123), nil, true},
{float64(12.3), nil, true},
{func() {}, nil, true},
}

for i, tt := range tests {
msg := fmt.Sprintf("i = %d, input[%+v], expect[%+v], isErr[%v]", i, tt.input, tt.expect, tt.isErr)

v, err := cvt.SliceE(tt.input)
if tt.isErr {
assert.Error(t, err, msg)
continue
}

assert.NoError(t, err, msg)
assert.Equal(t, tt.expect, v, msg)

// Non-E test with no default value:
v = cvt.Slice(tt.input)
assert.Equal(t, tt.expect, v, msg)
}
}

0 comments on commit a8ada29

Please sign in to comment.