From a8ada29c438a74cef954cbcdcff2e5c35e46e671 Mon Sep 17 00:00:00 2001 From: shockerli Date: Wed, 10 Mar 2021 19:01:43 +0800 Subject: [PATCH] add Slice()/SliceE() --- cvt.go | 13 +++++++ cvt_test.go | 42 +++++++++++++++++++++ cvte.go | 104 +++++++++++++++++++++++++++++++++++++-------------- cvte_test.go | 87 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 28 deletions(-) diff --git a/cvt.go b/cvt.go index f739769..789dccf 100644 --- a/cvt.go +++ b/cvt.go @@ -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 +} diff --git a/cvt_test.go b/cvt_test.go index 78ede2b..bd5f7a4 100644 --- a/cvt_test.go +++ b/cvt_test.go @@ -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) + } +} diff --git a/cvte.go b/cvte.go index a809047..e2f26bf 100644 --- a/cvte.go +++ b/cvte.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "reflect" + "sort" "strconv" "strings" ) @@ -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 @@ -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 diff --git a/cvte_test.go b/cvte_test.go index ddd4429..2f98dc5 100644 --- a/cvte_test.go +++ b/cvte_test.go @@ -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{} @@ -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) + } +}