Skip to content

Commit

Permalink
KeysE() support fields of struct
Browse files Browse the repository at this point in the history
  • Loading branch information
shockerli committed Nov 17, 2021
1 parent 4ee9c78 commit 44ba0dc
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 8 deletions.
6 changes: 6 additions & 0 deletions bool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ func TestBoolE(t *testing.T) {
{&aliasTypeStringOn, true, false},
{aliasTypeBool4True, true, false},
{&aliasTypeBool4True, true, false},
{pointerInterNil, false, false},
{&pointerInterNil, false, false},
{pointerIntNil, false, false},
{&pointerIntNil, false, false},
{(*AliasTypeInt)(nil), false, false},
{(*PointerTypeInt)(nil), false, false},

// true/slice/array/map
{[]int{1, 2, 3}, true, false},
Expand Down
47 changes: 47 additions & 0 deletions cvte.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,44 @@ func deepStructValues(rv reflect.Value) (sl []interface{}) {
return
}

// return the name of struct fields, and deep find the embedded fields
func deepStructFields(rt reflect.Type) (sl []string) {
rt = ptrType(rt)

type fff struct {
level int8
index int
}
var exists = make(map[string]fff)

fn := func(v string, level int8) {
ff, ok := exists[v]
if ok && ff.level <= level {
return
} else if ok && ff.level > level {
sl = append(sl[:ff.index], sl[ff.index+1:]...)
}
sl = append(sl, v)
exists[v] = fff{level, len(sl) - 1}
}

// sort by field definition order, include embed field
for j := 0; j < rt.NumField(); j++ {
f := rt.Field(j)
t := ptrType(f.Type)
// embed struct, include pointer struct
if f.Anonymous && t.Kind() == reflect.Struct {
for _, v := range deepStructFields(t) {
fn(v, 1)
}
} else { // single field, include pointer field
fn(f.Name, 0)
}
}

return
}

// return the map keys, which sorted by asc
func sortedMapKeys(v reflect.Value) (s []reflect.Value) {
s = v.MapKeys()
Expand All @@ -62,6 +100,15 @@ func sortedMapKeys(v reflect.Value) (s []reflect.Value) {
return
}

func ptrType(rt reflect.Type) reflect.Type {
if rt.Kind() == reflect.Ptr {
for rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
}
return rt
}

// returns the value with base type
func indirect(a interface{}) (val interface{}, rv reflect.Value) {
if a == nil {
Expand Down
33 changes: 32 additions & 1 deletion cvte_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
type (
AliasTypeBool bool
AliasTypeInt int
PointerTypeInt *AliasTypeInt
AliasTypeInt8 int8
AliasTypeInt16 int16
AliasTypeInt32 int32
Expand Down Expand Up @@ -53,6 +54,7 @@ var (

pointerRunes = []rune("中国")
pointerInterNil *AliasTypeInterface
pointerIntNil *AliasTypeInt
AliasTypeBytesNil AliasTypeBytes
)

Expand Down Expand Up @@ -118,6 +120,35 @@ func TestFieldE(t *testing.T) {
{TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "D1", 1, false},
{TestStructE{D1: 1, DD: &TestStructD{D1: 2}}, "DD", &TestStructD{D1: 2}, false},
{TestStructB{B1: 1, TestStructC: TestStructC{C1: "c1"}}, "C1", "c1", false},
{&TestStructB{B1: 1, TestStructC: TestStructC{C1: "c1"}}, "C1", "c1", false},
{struct {
*TestStructC
}{
&TestStructC{C1: "c1"},
}, "C1", "c1", false},
{&struct {
TestStructC
}{
TestStructC{C1: "c1"},
}, "C1", "c1", false},
{&struct {
*TestStructC
}{
&TestStructC{C1: "c1"},
}, "C1", "c1", false},
{&struct {
TestStructC
}{
TestStructC{C1: "c1"},
}, "TestStructC", TestStructC{C1: "c1"}, false},
{struct {
*TestStructC
}{
&TestStructC{C1: "c1"},
}, "TestStructC", &TestStructC{C1: "c1"}, false},
{struct {
AliasTypeInt
}{8}, "AliasTypeInt", AliasTypeInt(8), false},
{map[int]interface{}{123: "112233"}, "123", "112233", false},
{map[int]interface{}{123: "112233"}, 123, "112233", false},
{map[string]interface{}{"123": "112233"}, 123, "112233", false},
Expand Down Expand Up @@ -150,7 +181,7 @@ func TestFieldE(t *testing.T) {
}
}

////////////////////////////////////////////////////////////////////////////////
/* ------------------------------------------------------------------------------ */

// [testing assert functions]

Expand Down
12 changes: 12 additions & 0 deletions float_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,12 @@ func TestFloat64E(t *testing.T) {
{&aliasTypeBool4True, 1, false},
{aliasTypeBool4False, 0, false},
{&aliasTypeBool4False, 0, false},
{pointerInterNil, 0, false},
{&pointerInterNil, 0, false},
{pointerIntNil, 0, false},
{&pointerIntNil, 0, false},
{(*AliasTypeInt)(nil), 0, false},
{(*PointerTypeInt)(nil), 0, false},

// errors
{"10a", 0, true},
Expand Down Expand Up @@ -342,6 +348,12 @@ func TestFloat32E(t *testing.T) {
{&aliasTypeBool4True, 1, false},
{aliasTypeBool4False, 0, false},
{&aliasTypeBool4False, 0, false},
{pointerInterNil, 0, false},
{&pointerInterNil, 0, false},
{pointerIntNil, 0, false},
{&pointerIntNil, 0, false},
{(*AliasTypeInt)(nil), 0, false},
{(*PointerTypeInt)(nil), 0, false},

// errors
{"10a", 0, true},
Expand Down
6 changes: 6 additions & 0 deletions int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,7 @@ func TestUint64E(t *testing.T) {
{AliasTypeBytes("10.98"), 10, false},
{json.Number("1"), 1, false},
{pointerInterNil, 0, false},
{&pointerInterNil, 0, false},

// errors
{int(-8), 0, true},
Expand Down Expand Up @@ -1613,6 +1614,11 @@ func TestInt64E(t *testing.T) {
{&aliasTypeBool4False, 0, false},
{json.Number("1"), 1, false},
{pointerInterNil, 0, false},
{&pointerInterNil, 0, false},
{pointerIntNil, 0, false},
{&pointerIntNil, 0, false},
{(*AliasTypeInt)(nil), 0, false},
{(*PointerTypeInt)(nil), 0, false},

// errors
{"10a", 0, true},
Expand Down
12 changes: 6 additions & 6 deletions slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func ColumnsE(val interface{}, field interface{}) (sl []interface{}, err error)
return nil, fmt.Errorf("unsupported type: %s", rv.Type().Name())
}

// KeysE return the keys of map, sorted by asc
// KeysE return the keys of map, sorted by asc; or fields of struct
func KeysE(val interface{}) (sl []interface{}, err error) {
if val == nil {
return nil, errUnsupportedTypeNil
Expand All @@ -169,11 +169,11 @@ func KeysE(val interface{}) (sl []interface{}, err error) {
sl = append(sl, key.Interface())
}
return
// case reflect.Struct:
// for _, v := range deepStructFields(rv.Type()) {
// sl = append(sl, v)
// }
// return
case reflect.Struct:
for _, v := range deepStructFields(rv.Type()) {
sl = append(sl, v)
}
return
}

return nil, fmt.Errorf("unsupported type: %s", rv.Type().Name())
Expand Down
57 changes: 56 additions & 1 deletion slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,15 +368,70 @@ func TestKeysE(t *testing.T) {
expect []interface{}
isErr bool
}{
// map
{map[int]map[string]interface{}{1: {"1": 111, "DDD": 12.3}, 2: {"2": 222, "DDD": "321"}, 3: {"DDD": nil}}, []interface{}{1, 2, 3}, false},
{map[string]interface{}{"A": 1, "2": 2}, []interface{}{"2", "A"}, false},
{map[float64]TestStructD{1: {11}, 2: {22}}, []interface{}{float64(1), float64(2)}, false},
{map[interface{}]interface{}{1: 1, 2.2: 2.22, "A": "A"}, []interface{}{1, 2.2, "A"}, false},

// struct
{TestStructA{}, []interface{}{"A1", "C1", "B1", "A2", "DD"}, false},
{&TestStructB{}, []interface{}{"C1", "B1"}, false},
{struct {
A1 int
TestStructC
B1 int
C1 int
}{}, []interface{}{"A1", "B1", "C1"}, false},
{struct {
A1 int
*TestStructC
B1 int
C1 int
}{}, []interface{}{"A1", "B1", "C1"}, false},
{struct {
A1 int
C1 int
TestStructC
B1 int
}{}, []interface{}{"A1", "C1", "B1"}, false},
{struct {
A1 int
C1 int
*TestStructC
B1 int
}{}, []interface{}{"A1", "C1", "B1"}, false},
{struct {
a1 int
TestStructC
c2 int
B1 int
}{}, []interface{}{"a1", "C1", "c2", "B1"}, false},
{struct {
*TestStructC
B1 int
}{}, []interface{}{"C1", "B1"}, false},
{&struct {
*TestStructC
B1 int
}{}, []interface{}{"C1", "B1"}, false},
{&struct {
B1 *int
}{}, []interface{}{"B1"}, false},
{&struct {
AliasTypeString
}{}, []interface{}{"AliasTypeString"}, false},
{struct {
*AliasTypeString
K *int
}{}, []interface{}{"AliasTypeString", "K"}, false},

// errors
{nil, nil, true},
{"Name", nil, true},
{testing.T{}, nil, true},
{8, nil, true},
{8.88, nil, true},
{[]byte{'a', 'b'}, nil, true},
}

for i, tt := range tests {
Expand Down
6 changes: 6 additions & 0 deletions string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ func TestStringE(t *testing.T) {
{template.URL("https://host.foo"), "https://host.foo", false},
{template.HTML("<html></html>"), "<html></html>", false},
{json.Number("12.34"), "12.34", false},
{pointerInterNil, "", false},
{&pointerInterNil, "", false},
{pointerIntNil, "", false},
{&pointerIntNil, "", false},
{(*AliasTypeInt)(nil), "", false},
{(*PointerTypeInt)(nil), "", false},

// errors
{testing.T{}, "", true},
Expand Down

0 comments on commit 44ba0dc

Please sign in to comment.