From 4ae89dc9f62ced2aaf3c7eeb2eaf438c65c1521c Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 30 Oct 2020 22:04:34 +0800 Subject: [PATCH] improve package internal/structs --- database/gdb/gdb_func.go | 6 ++--- internal/structs/structs_tag.go | 25 ++++++++----------- internal/structs/structs_test.go | 34 +++++++++++++------------- util/gconv/gconv_struct.go | 2 +- util/gconv/gconv_z_unit_struct_test.go | 28 +++++++++++++++++++++ util/gvalid/gvalid_check_struct.go | 2 +- 6 files changed, 60 insertions(+), 37 deletions(-) diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 860f4da51..4f22c5eb9 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -314,7 +314,7 @@ func doQuoteString(s, charLeft, charRight string) string { // This function automatically retrieves primary or unique field and its attribute value as condition. func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) { array := ([]string)(nil) - for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) { + for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) { array = strings.Split(field.Tag, ",") if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) { return array[0], []interface{}{field.Value()} @@ -331,7 +331,7 @@ func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interf // GetPrimaryKey retrieves and returns primary key field name from given struct. func GetPrimaryKey(pointer interface{}) string { array := ([]string)(nil) - for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) { + for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) { array = strings.Split(field.Tag, ",") if len(array) > 1 && array[1] == ORM_TAG_FOR_PRIMARY { return array[0] @@ -723,7 +723,7 @@ func FormatSqlWithArgs(sql string, args []interface{}) string { func mapToStruct(data map[string]interface{}, pointer interface{}) error { // It retrieves and returns the mapping between orm tag and the struct attribute name. mapping := make(map[string]string) - for tag, attr := range structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}, true) { + for tag, attr := range structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}) { mapping[strings.Split(tag, ",")[0]] = attr } return gconv.StructDeep(data, pointer, mapping) diff --git a/internal/structs/structs_tag.go b/internal/structs/structs_tag.go index 544068d3f..08e3ecb6b 100644 --- a/internal/structs/structs_tag.go +++ b/internal/structs/structs_tag.go @@ -16,17 +16,15 @@ import ( // // The parameter should be type of struct/*struct. // -// The parameter specifies whether retrieving the struct field recursively. -// // Note that it only retrieves the exported attributes with first letter up-case from struct. -func TagFields(pointer interface{}, priority []string, recursive bool) []*Field { - return doTagFields(pointer, priority, recursive, map[string]struct{}{}) +func TagFields(pointer interface{}, priority []string) []*Field { + return doTagFields(pointer, priority, map[string]struct{}{}) } // doTagFields retrieves the tag and corresponding attribute name from . It also filters repeated // tag internally. // The parameter should be type of struct/*struct. -func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap map[string]struct{}) []*Field { +func doTagFields(pointer interface{}, priority []string, tagMap map[string]struct{}) []*Field { // If points to an invalid address, for example a nil variable, // it here creates an empty struct using reflect feature. var ( @@ -90,7 +88,8 @@ func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap Tag: tag, }) } - if recursive { + // If this is an embedded attribute, it retrieves the tags recursively. + if field.IsEmbedded() { var ( rv = reflect.ValueOf(field.Value()) kind = rv.Kind() @@ -100,7 +99,7 @@ func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap kind = rv.Kind() } if kind == reflect.Struct { - tagFields = append(tagFields, doTagFields(rv, priority, recursive, tagMap)...) + tagFields = append(tagFields, doTagFields(rv, priority, tagMap)...) } } } @@ -111,11 +110,9 @@ func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap // // The parameter should be type of struct/*struct. // -// The parameter specifies whether retrieving the struct field recursively. -// // Note that it only retrieves the exported attributes with first letter up-case from struct. -func TagMapName(pointer interface{}, priority []string, recursive bool) map[string]string { - fields := TagFields(pointer, priority, recursive) +func TagMapName(pointer interface{}, priority []string) map[string]string { + fields := TagFields(pointer, priority) tagMap := make(map[string]string, len(fields)) for _, v := range fields { tagMap[v.Tag] = v.Name() @@ -127,11 +124,9 @@ func TagMapName(pointer interface{}, priority []string, recursive bool) map[stri // // The parameter should be type of struct/*struct. // -// The parameter specifies whether retrieving the struct field recursively. -// // Note that it only retrieves the exported attributes with first letter up-case from struct. -func TagMapField(pointer interface{}, priority []string, recursive bool) map[string]*Field { - fields := TagFields(pointer, priority, recursive) +func TagMapField(pointer interface{}, priority []string) map[string]*Field { + fields := TagFields(pointer, priority) tagMap := make(map[string]*Field, len(fields)) for _, v := range fields { tagMap[v.Tag] = v diff --git a/internal/structs/structs_test.go b/internal/structs/structs_test.go index ae5c2dd08..f1840d62b 100644 --- a/internal/structs/structs_test.go +++ b/internal/structs/structs_test.go @@ -24,12 +24,12 @@ func Test_Basic(t *testing.T) { Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` } var user User - t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"}) + t.Assert(structs.TagMapName(user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}), g.Map{"name": "Name", "pass": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}), g.Map{"name": "Name", "pass1": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}), g.Map{"name": "Name", "pass2": "Pass"}) }) gtest.C(t, func(t *gtest.T) { @@ -43,7 +43,7 @@ func Test_Basic(t *testing.T) { Base `params:"base"` } user := new(UserWithBase) - t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{ + t.Assert(structs.TagMapName(user, []string{"params"}), g.Map{ "base": "Base", "password1": "Pass1", "password2": "Pass2", @@ -55,20 +55,20 @@ func Test_Basic(t *testing.T) { Pass1 string `params:"password1"` Pass2 string `params:"password2"` } - type UserWithBase1 struct { + type UserWithEmbeddedAttribute struct { Id int Name string Base } - type UserWithBase2 struct { + type UserWithoutEmbeddedAttribute struct { Id int Name string Pass Base } - user1 := new(UserWithBase1) - user2 := new(UserWithBase2) - t.Assert(structs.TagMapName(user1, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"}) - t.Assert(structs.TagMapName(user2, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"}) + user1 := new(UserWithEmbeddedAttribute) + user2 := new(UserWithoutEmbeddedAttribute) + t.Assert(structs.TagMapName(user1, []string{"params"}), g.Map{"password1": "Pass1", "password2": "Pass2"}) + t.Assert(structs.TagMapName(user2, []string{"params"}), g.Map{}) }) } @@ -80,11 +80,11 @@ func Test_StructOfNilPointer(t *testing.T) { Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` } var user *User - t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"}) + t.Assert(structs.TagMapName(user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}), g.Map{"name": "Name", "pass": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}), g.Map{"name": "Name", "pass1": "Pass"}) + t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}), g.Map{"name": "Name", "pass2": "Pass"}) }) } diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index d08ace23a..d06e608c8 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -191,7 +191,7 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping . // The key of the tagMap is the attribute name of the struct, // and the value is its replaced tag name for later comparison to improve performance. tagMap := make(map[string]string) - for k, v := range structs.TagMapName(pointer, StructTagPriority, true) { + for k, v := range structs.TagMapName(pointer, StructTagPriority) { tagMap[v] = replaceCharReg.ReplaceAllString(k, "") } diff --git a/util/gconv/gconv_z_unit_struct_test.go b/util/gconv/gconv_z_unit_struct_test.go index f423d8010..a04ef87c6 100644 --- a/util/gconv/gconv_z_unit_struct_test.go +++ b/util/gconv/gconv_z_unit_struct_test.go @@ -1001,3 +1001,31 @@ func Test_Struct_WithJson(t *testing.T) { t.Assert(b2, b1) }) } + +func Test_Struct_AttrStructHasTheSameTag(t *testing.T) { + type Product struct { + Id int `json:"id"` + UpdatedAt time.Time `json:"-" ` + UpdatedAtFormat string `json:"updated_at" ` + } + + type Order struct { + Id int `json:"id"` + UpdatedAt time.Time `json:"updated_at"` + Product Product `json:"products"` + } + gtest.C(t, func(t *gtest.T) { + data := g.Map{ + "id": 1, + "updated_at": time.Now(), + } + order := new(Order) + err := gconv.Struct(data, order) + t.Assert(err, nil) + t.Assert(order.Id, data["id"]) + t.Assert(order.UpdatedAt, data["updated_at"]) + t.Assert(order.Product.Id, 0) + t.Assert(order.Product.UpdatedAt.IsZero(), true) + t.Assert(order.Product.UpdatedAtFormat, "") + }) +} diff --git a/util/gvalid/gvalid_check_struct.go b/util/gvalid/gvalid_check_struct.go index 3f0bfd271..6d2d4e67a 100644 --- a/util/gvalid/gvalid_check_struct.go +++ b/util/gvalid/gvalid_check_struct.go @@ -78,7 +78,7 @@ func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) * params[field.Name()] = field.Value() } // It here must use structs.TagFields not structs.MapField to ensure error sequence. - for _, field := range structs.TagFields(object, structTagPriority, true) { + for _, field := range structs.TagFields(object, structTagPriority) { fieldName := field.Name() // sequence tag == struct tag // The name here is alias of field name.