diff --git a/cvte.go b/cvte.go index 1d231a1..6345332 100644 --- a/cvte.go +++ b/cvte.go @@ -13,6 +13,7 @@ import ( var errConvFail = errors.New("convert failed") var errFieldNotFound = errors.New("field not found") +var errUnsupportedTypeNil = errors.New("unsupported type: nil") var formatOutOfLimitInt = "%w, out of max limit value(%d)" var formatOutOfLimitFloat = "%w, out of max limit value(%f)" var formatExtend = "%v, %w" @@ -359,7 +360,7 @@ func StringE(val interface{}) (string, error) { // SliceE convert an interface to a []interface{} type func SliceE(val interface{}) (sl []interface{}, err error) { if val == nil { - return + return nil, errUnsupportedTypeNil } _, rt, rv := Indirect(val) @@ -376,12 +377,7 @@ func SliceE(val interface{}) (sl []interface{}, err error) { } 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 { + for _, key := range sortedMapKeys(rv) { sl = append(sl, rv.MapIndex(key).Interface()) } return @@ -405,8 +401,21 @@ func deepStructValues(rt reflect.Type, rv reflect.Value) (sl []interface{}) { return } +// return the map keys, which sorted by asc +func sortedMapKeys(v reflect.Value) (s []reflect.Value) { + s = v.MapKeys() + sort.Slice(s, func(i, j int) bool { + return strings.Compare(String(s[i].Interface()), String(s[j].Interface())) < 0 + }) + return +} + // FieldE return the field value from map/struct, ignore the filed type func FieldE(val interface{}, field interface{}) (interface{}, error) { + if val == nil { + return nil, errUnsupportedTypeNil + } + sf := String(field) // match with the String of field, so field can be any type _, rt, rv := Indirect(val) @@ -427,6 +436,39 @@ func FieldE(val interface{}, field interface{}) (interface{}, error) { return nil, fmt.Errorf("%w(%s)", errFieldNotFound, sf) } +// ColumnsE return the values from a single column in the input array/slice/map of struct/map +func ColumnsE(val interface{}, field interface{}) (sl []interface{}, err error) { + if val == nil { + return nil, errUnsupportedTypeNil + } + + _, rt, rv := Indirect(val) + + switch rt.Kind() { + case reflect.Slice, reflect.Array: + for j := 0; j < rv.Len(); j++ { + vv, e := FieldE(rv.Index(j).Interface(), field) + if e == nil { + sl = append(sl, vv) + } + } + case reflect.Map: + for _, key := range sortedMapKeys(rv) { + vv, e := FieldE(rv.MapIndex(key).Interface(), field) + if e == nil { + sl = append(sl, vv) + } + } + } + + // non valid field value, means error + if len(sl) > 0 { + return + } + + return nil, fmt.Errorf("unsupported type: %s", rt.Kind()) +} + // Indirect returns the value with base type func Indirect(a interface{}) (val interface{}, rt reflect.Type, rv reflect.Value) { if a == nil { diff --git a/cvte_test.go b/cvte_test.go index 0682fe8..d40a2f9 100644 --- a/cvte_test.go +++ b/cvte_test.go @@ -1378,6 +1378,7 @@ func TestSliceE(t *testing.T) { {uint16(123), nil, true}, {float64(12.3), nil, true}, {func() {}, nil, true}, + {nil, nil, true}, } for i, tt := range tests { @@ -1420,6 +1421,7 @@ func TestFieldE(t *testing.T) { {uint16(123), "Name", nil, true}, {float64(12.3), "Name", nil, true}, {func() {}, "Name", nil, true}, + {nil, "Name", nil, true}, } for i, tt := range tests { @@ -1438,3 +1440,53 @@ func TestFieldE(t *testing.T) { assert.Equal(t, tt.expect, v, msg) } } + +func TestColumnsE(t *testing.T) { + tests := []struct { + input interface{} + field interface{} + expect interface{} + isErr bool + }{ + {[]interface{}{TestStructE{D1: 1, DD: &TestStructD{D1: 2}}}, "D1", []interface{}{1}, false}, + {[]TestStructE{{D1: 1}, {D1: 2}}, "D1", []interface{}{1, 2}, false}, + {[]TestStructE{{DD: &TestStructD{}}, {D1: 2}}, "DD", []interface{}{&TestStructD{}, (*TestStructD)(nil)}, false}, + {[]interface{}{TestStructE{D1: 1, DD: &TestStructD{D1: 2}}}, "DD", []interface{}{&TestStructD{D1: 2}}, false}, + {[]map[string]interface{}{{"1": 111, "DDD": "D1"}, {"2": 222, "DDD": "D2"}, {"DDD": nil}}, "DDD", []interface{}{"D1", "D2", nil}, false}, + {map[int]map[string]interface{}{1: {"1": 111, "DDD": "D1"}, 2: {"2": 222, "DDD": "D2"}, 3: {"DDD": nil}}, "DDD", []interface{}{"D1", "D2", nil}, false}, + {map[int]TestStructD{1: {11}, 2: {22}}, "D1", []interface{}{11, 22}, false}, + + // errors + {TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "", nil, true}, + {TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "Age", nil, true}, + {int(123), "Name", nil, true}, + {uint16(123), "Name", nil, true}, + {float64(12.3), "Name", nil, true}, + {"Name", "Name", nil, true}, + {func() {}, "Name", nil, true}, + {nil, "Name", nil, true}, + {TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "D1", nil, true}, + {TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "DD", nil, true}, + {TestStructB{B1: 1, TestStructC: TestStructC{C1: "c1"}}, "C1", nil, true}, + {map[int]interface{}{123: "112233"}, "123", nil, true}, + {map[int]interface{}{123: "112233"}, 123, nil, true}, + {map[string]interface{}{"123": "112233"}, 123, nil, true}, + {map[string]interface{}{"c": "ccc"}, TestStructC{C1: "c"}, nil, true}, + } + + for i, tt := range tests { + msg := fmt.Sprintf( + "i = %d, input[%+v], field[%s], expect[%+v], isErr[%v]", + i, tt.input, tt.field, tt.expect, tt.isErr, + ) + + v, err := cvt.ColumnsE(tt.input, tt.field) + if tt.isErr { + assert.Error(t, err, msg) + continue + } + + assert.NoError(t, err, msg) + assert.Equal(t, tt.expect, v, msg) + } +}