From c02bf715c53db154970d4929a95cc15ce016dcd7 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 26 Nov 2020 21:13:44 +0800 Subject: [PATCH] improve gutil.ListItemValues/ListItemValuesUnique supporting retrieving values from slice attributes --- util/gutil/gutil_list.go | 37 +++++---- util/gutil/gutil_z_unit_list_test.go | 112 ++++++++++++++++++++++++++- 2 files changed, 135 insertions(+), 14 deletions(-) diff --git a/util/gutil/gutil_list.go b/util/gutil/gutil_list.go index 501abc221..5be42b682 100644 --- a/util/gutil/gutil_list.go +++ b/util/gutil/gutil_list.go @@ -21,26 +21,34 @@ import ( // []struct:sub-struct // Note that the sub-map/sub-struct makes sense only if the optional parameter is given. func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) { - var ( + var reflectValue reflect.Value + if v, ok := list.(reflect.Value); ok { + reflectValue = v + } else { reflectValue = reflect.ValueOf(list) - reflectKind = reflectValue.Kind() - ) + } + reflectKind := reflectValue.Kind() for reflectKind == reflect.Ptr { reflectValue = reflectValue.Elem() reflectKind = reflectValue.Kind() } - values = []interface{}{} switch reflectKind { case reflect.Slice, reflect.Array: if reflectValue.Len() == 0 { return } + values = []interface{}{} for i := 0; i < reflectValue.Len(); i++ { - if value, ok := doItemValue(reflectValue.Index(i), key); ok { + if value, ok := ItemValue(reflectValue.Index(i), key); ok { if len(subKey) > 0 && subKey[0] != nil { - if subValue, ok := doItemValue(value, subKey[0]); ok { - values = append(values, subValue) + if subValue, ok := ItemValue(value, subKey[0]); ok { + value = subValue + } else { + continue } + } + if array, ok := value.([]interface{}); ok { + values = append(values, array...) } else { values = append(values, value) } @@ -52,12 +60,7 @@ func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (v // ItemValue retrieves and returns its value of which name/attribute specified by . // The parameter can be type of map/*map/struct/*struct. -func ItemValue(item interface{}, key interface{}) (value interface{}) { - value, _ = doItemValue(item, key) - return -} - -func doItemValue(item interface{}, key interface{}) (value interface{}, found bool) { +func ItemValue(item interface{}, key interface{}) (value interface{}, found bool) { var reflectValue reflect.Value if v, ok := item.(reflect.Value); ok { reflectValue = v @@ -80,6 +83,14 @@ func doItemValue(item interface{}, key interface{}) (value interface{}, found bo keyValue = reflect.ValueOf(key) } switch reflectKind { + case reflect.Array, reflect.Slice: + // The must be type of string. + values := ListItemValues(reflectValue, keyValue.String()) + if values == nil { + return nil, false + } + return values, true + case reflect.Map: v := reflectValue.MapIndex(keyValue) if v.IsValid() { diff --git a/util/gutil/gutil_z_unit_list_test.go b/util/gutil/gutil_z_unit_list_test.go index 20c46421a..408707fea 100755 --- a/util/gutil/gutil_z_unit_list_test.go +++ b/util/gutil/gutil_z_unit_list_test.go @@ -35,7 +35,7 @@ func Test_ListItemValues_Map(t *testing.T) { }) } -func Test_ListItemValues_SubKey(t *testing.T) { +func Test_ListItemValues_Map_SubKey(t *testing.T) { type Scores struct { Math int English int @@ -52,6 +52,23 @@ func Test_ListItemValues_SubKey(t *testing.T) { }) } +func Test_ListItemValues_Map_Array_SubKey(t *testing.T) { + type Scores struct { + Math int + English int + } + gtest.C(t, func(t *gtest.T) { + listMap := g.List{ + g.Map{"id": 1, "scores": []Scores{{1, 2}, {3, 4}}}, + g.Map{"id": 2, "scores": []Scores{{5, 6}, {7, 8}}}, + g.Map{"id": 3, "scores": []Scores{{9, 10}, {11, 12}}}, + } + t.Assert(gutil.ListItemValues(listMap, "scores", "Math"), g.Slice{1, 3, 5, 7, 9, 11}) + t.Assert(gutil.ListItemValues(listMap, "scores", "English"), g.Slice{2, 4, 6, 8, 10, 12}) + t.Assert(gutil.ListItemValues(listMap, "scores", "PE"), g.Slice{}) + }) +} + func Test_ListItemValues_Struct(t *testing.T) { gtest.C(t, func(t *gtest.T) { type T struct { @@ -96,6 +113,45 @@ func Test_ListItemValues_Struct(t *testing.T) { }) } +func Test_ListItemValues_Struct_SubKey(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type Student struct { + Id int + Score float64 + } + type Class struct { + Total int + Students []Student + } + listStruct := g.Slice{ + Class{2, []Student{{1, 1}, {2, 2}}}, + Class{3, []Student{{3, 3}, {4, 4}, {5, 5}}}, + Class{1, []Student{{6, 6}}}, + } + t.Assert(gutil.ListItemValues(listStruct, "Total"), g.Slice{2, 3, 1}) + t.Assert(gutil.ListItemValues(listStruct, "Students"), `[[{"Id":1,"Score":1},{"Id":2,"Score":2}],[{"Id":3,"Score":3},{"Id":4,"Score":4},{"Id":5,"Score":5}],[{"Id":6,"Score":6}]]`) + t.Assert(gutil.ListItemValues(listStruct, "Students", "Id"), g.Slice{1, 2, 3, 4, 5, 6}) + }) + gtest.C(t, func(t *gtest.T) { + type Student struct { + Id int + Score float64 + } + type Class struct { + Total int + Students []*Student + } + listStruct := g.Slice{ + &Class{2, []*Student{{1, 1}, {2, 2}}}, + &Class{3, []*Student{{3, 3}, {4, 4}, {5, 5}}}, + &Class{1, []*Student{{6, 6}}}, + } + t.Assert(gutil.ListItemValues(listStruct, "Total"), g.Slice{2, 3, 1}) + t.Assert(gutil.ListItemValues(listStruct, "Students"), `[[{"Id":1,"Score":1},{"Id":2,"Score":2}],[{"Id":3,"Score":3},{"Id":4,"Score":4},{"Id":5,"Score":5}],[{"Id":6,"Score":6}]]`) + t.Assert(gutil.ListItemValues(listStruct, "Students", "Id"), g.Slice{1, 2, 3, 4, 5, 6}) + }) +} + func Test_ListItemValuesUnique(t *testing.T) { gtest.C(t, func(t *gtest.T) { listMap := g.List{ @@ -131,3 +187,57 @@ func Test_ListItemValuesUnique(t *testing.T) { t.Assert(gutil.ListItemValuesUnique(listMap, "score"), g.Slice{100, 0, 99}) }) } + +func Test_ListItemValuesUnique_Struct_SubKey(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type Student struct { + Id int + Score float64 + } + type Class struct { + Total int + Students []Student + } + listStruct := g.Slice{ + Class{2, []Student{{1, 1}, {1, 2}}}, + Class{3, []Student{{2, 3}, {2, 4}, {5, 5}}}, + Class{1, []Student{{6, 6}}}, + } + t.Assert(gutil.ListItemValuesUnique(listStruct, "Total"), g.Slice{2, 3, 1}) + t.Assert(gutil.ListItemValuesUnique(listStruct, "Students", "Id"), g.Slice{1, 2, 5, 6}) + }) + gtest.C(t, func(t *gtest.T) { + type Student struct { + Id int + Score float64 + } + type Class struct { + Total int + Students []*Student + } + listStruct := g.Slice{ + &Class{2, []*Student{{1, 1}, {1, 2}}}, + &Class{3, []*Student{{2, 3}, {2, 4}, {5, 5}}}, + &Class{1, []*Student{{6, 6}}}, + } + t.Assert(gutil.ListItemValuesUnique(listStruct, "Total"), g.Slice{2, 3, 1}) + t.Assert(gutil.ListItemValuesUnique(listStruct, "Students", "Id"), g.Slice{1, 2, 5, 6}) + }) +} + +func Test_ListItemValuesUnique_Map_Array_SubKey(t *testing.T) { + type Scores struct { + Math int + English int + } + gtest.C(t, func(t *gtest.T) { + listMap := g.List{ + g.Map{"id": 1, "scores": []Scores{{1, 2}, {1, 2}}}, + g.Map{"id": 2, "scores": []Scores{{5, 8}, {5, 8}}}, + g.Map{"id": 3, "scores": []Scores{{9, 10}, {11, 12}}}, + } + t.Assert(gutil.ListItemValuesUnique(listMap, "scores", "Math"), g.Slice{1, 5, 9, 11}) + t.Assert(gutil.ListItemValuesUnique(listMap, "scores", "English"), g.Slice{2, 8, 10, 12}) + t.Assert(gutil.ListItemValuesUnique(listMap, "scores", "PE"), g.Slice{}) + }) +}