From 44ba0dcec49ead2aaa95cf1837b146f39bfd3209 Mon Sep 17 00:00:00 2001 From: shockerli Date: Wed, 17 Nov 2021 14:31:37 +0800 Subject: [PATCH] `KeysE()` support fields of struct --- bool_test.go | 6 ++++++ cvte.go | 47 +++++++++++++++++++++++++++++++++++++++++ cvte_test.go | 33 ++++++++++++++++++++++++++++- float_test.go | 12 +++++++++++ int_test.go | 6 ++++++ slice.go | 12 +++++------ slice_test.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++- string_test.go | 6 ++++++ 8 files changed, 171 insertions(+), 8 deletions(-) diff --git a/bool_test.go b/bool_test.go index 6338b31..aa9055e 100644 --- a/bool_test.go +++ b/bool_test.go @@ -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}, diff --git a/cvte.go b/cvte.go index 6a9f7f0..198b526 100644 --- a/cvte.go +++ b/cvte.go @@ -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() @@ -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 { diff --git a/cvte_test.go b/cvte_test.go index 693651e..a436c3e 100644 --- a/cvte_test.go +++ b/cvte_test.go @@ -18,6 +18,7 @@ import ( type ( AliasTypeBool bool AliasTypeInt int + PointerTypeInt *AliasTypeInt AliasTypeInt8 int8 AliasTypeInt16 int16 AliasTypeInt32 int32 @@ -53,6 +54,7 @@ var ( pointerRunes = []rune("中国") pointerInterNil *AliasTypeInterface + pointerIntNil *AliasTypeInt AliasTypeBytesNil AliasTypeBytes ) @@ -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}, @@ -150,7 +181,7 @@ func TestFieldE(t *testing.T) { } } -//////////////////////////////////////////////////////////////////////////////// +/* ------------------------------------------------------------------------------ */ // [testing assert functions] diff --git a/float_test.go b/float_test.go index 90c2095..5c61481 100644 --- a/float_test.go +++ b/float_test.go @@ -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}, @@ -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}, diff --git a/int_test.go b/int_test.go index 2c3f773..a0d8214 100644 --- a/int_test.go +++ b/int_test.go @@ -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}, @@ -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}, diff --git a/slice.go b/slice.go index dc48359..7424136 100644 --- a/slice.go +++ b/slice.go @@ -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 @@ -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()) diff --git a/slice_test.go b/slice_test.go index a789887..24af2fe 100644 --- a/slice_test.go +++ b/slice_test.go @@ -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 { diff --git a/string_test.go b/string_test.go index c35d1db..a3ddcdc 100644 --- a/string_test.go +++ b/string_test.go @@ -151,6 +151,12 @@ func TestStringE(t *testing.T) { {template.URL("https://host.foo"), "https://host.foo", false}, {template.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},