improve package internal/structs

This commit is contained in:
Jack
2020-10-30 22:04:34 +08:00
parent 4f6f07db1d
commit 4ae89dc9f6
6 changed files with 60 additions and 37 deletions

View File

@ -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)

View File

@ -16,17 +16,15 @@ import (
//
// The parameter <pointer> should be type of struct/*struct.
//
// The parameter <recursive> 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 <pointer>. It also filters repeated
// tag internally.
// The parameter <pointer> 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 <pointer> 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 <pointer> should be type of struct/*struct.
//
// The parameter <recursive> 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 <pointer> should be type of struct/*struct.
//
// The parameter <recursive> 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

View File

@ -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"})
})
}

View File

@ -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, "")
}

View File

@ -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, "")
})
}

View File

@ -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.