Skip to content

Commit

Permalink
add ColumnsE
Browse files Browse the repository at this point in the history
return the values from a single column in the input array/slice/map of struct/map
  • Loading branch information
shockerli committed Mar 12, 2021
1 parent 7648282 commit 0cbdee3
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 7 deletions.
56 changes: 49 additions & 7 deletions cvte.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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)

Expand All @@ -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 {
Expand Down
52 changes: 52 additions & 0 deletions cvte_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
}

0 comments on commit 0cbdee3

Please sign in to comment.