From d178102f82e2b58b97fd6d466e9a1e4219a01fa3 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 8 Nov 2020 14:25:17 +0800 Subject: [PATCH] remove third party package 'structs'; improve performance for package internal/structs --- database/gdb/gdb_func.go | 34 ++++-- encoding/ghtml/ghtml.go | 23 ++-- encoding/ghtml/ghtml_test.go | 1 + go.mod | 1 - internal/structs/structs.go | 40 ++++++- internal/structs/structs_map.go | 87 +++------------ internal/structs/structs_tag.go | 167 ++++++++++++++++------------- internal/structs/structs_test.go | 39 ++++--- util/gconv/gconv_struct.go | 11 +- util/gvalid/gvalid_check_struct.go | 14 ++- 10 files changed, 220 insertions(+), 197 deletions(-) diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 7baeb344f..dac2e82e6 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -312,32 +312,40 @@ func doQuoteString(s, charLeft, charRight string) string { // GetWhereConditionOfStruct returns the where condition sql and arguments by given struct pointer. // This function automatically retrieves primary or unique field and its attribute value as condition. -func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) { +func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}, err error) { + tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) + if err != nil { + return "", nil, err + } array := ([]string)(nil) - for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) { - array = strings.Split(field.Tag, ",") + for _, field := range tagField { + array = strings.Split(field.CurrentTag, ",") if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) { - return array[0], []interface{}{field.Value()} + return array[0], []interface{}{field.Value()}, nil } if len(where) > 0 { where += " " } - where += field.Tag + "=?" + where += field.CurrentTag + "=?" args = append(args, field.Value()) } return } // GetPrimaryKey retrieves and returns primary key field name from given struct. -func GetPrimaryKey(pointer interface{}) string { +func GetPrimaryKey(pointer interface{}) (string, error) { + tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) + if err != nil { + return "", err + } array := ([]string)(nil) - for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) { - array = strings.Split(field.Tag, ",") + for _, field := range tagField { + array = strings.Split(field.CurrentTag, ",") if len(array) > 1 && array[1] == ORM_TAG_FOR_PRIMARY { - return array[0] + return array[0], nil } } - return "" + return "", nil } // GetPrimaryKeyCondition returns a new where condition by primary field name. @@ -721,9 +729,13 @@ func FormatSqlWithArgs(sql string, args []interface{}) string { // mapToStruct maps the to given struct. // Note that the given parameter should be a pointer to s struct. func mapToStruct(data map[string]interface{}, pointer interface{}) error { + tagNameMap, err := structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}) + if err != nil { + return err + } // 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}) { + for tag, attr := range tagNameMap { mapping[strings.Split(tag, ",")[0]] = attr } return gconv.Struct(data, pointer, mapping) diff --git a/encoding/ghtml/ghtml.go b/encoding/ghtml/ghtml.go index 7f3500d48..205b107e7 100644 --- a/encoding/ghtml/ghtml.go +++ b/encoding/ghtml/ghtml.go @@ -14,27 +14,27 @@ import ( strip "github.com/grokify/html-strip-tags-go" ) -// 过滤掉HTML标签,只返回text内容 -// 参考:http://php.net/manual/zh/function.strip-tags.php +// StripTags strips HTML tags from content, and returns only text. +// Referer: http://php.net/manual/zh/function.strip-tags.php func StripTags(s string) string { return strip.StripTags(s) } -// 本函数各方面都和SpecialChars一样, -// 除了Entities会转换所有具有 HTML 实体的字符。 -// 参考:http://php.net/manual/zh/function.htmlentities.php +// Entities encodes all HTML chars for content. +// Referer: http://php.net/manual/zh/function.htmlentities.php func Entities(s string) string { return html.EscapeString(s) } -// Entities 的相反操作 -// 参考:http://php.net/manual/zh/function.html-entity-decode.php +// EntitiesDecode decodes all HTML chars for content. +// Referer: http://php.net/manual/zh/function.html-entity-decode.php func EntitiesDecode(s string) string { return html.UnescapeString(s) } -// 将html中的部分特殊标签转换为html转义标签 -// 参考:http://php.net/manual/zh/function.htmlspecialchars.php +// SpecialChars encodes some special chars for content, these special chars are: +// "&", "<", ">", `"`, "'". +// Referer: http://php.net/manual/zh/function.htmlspecialchars.php func SpecialChars(s string) string { return strings.NewReplacer( "&", "&", @@ -45,8 +45,9 @@ func SpecialChars(s string) string { ).Replace(s) } -// 将html部分转义标签还原为html特殊标签 -// 参考:http://php.net/manual/zh/function.htmlspecialchars-decode.php +// SpecialCharsDecode decodes some special chars for content, these special chars are: +// "&", "<", ">", `"`, "'". +// Referer: http://php.net/manual/zh/function.htmlspecialchars-decode.php func SpecialCharsDecode(s string) string { return strings.NewReplacer( "&", "&", diff --git a/encoding/ghtml/ghtml_test.go b/encoding/ghtml/ghtml_test.go index bbc29fb7e..6d215f08f 100644 --- a/encoding/ghtml/ghtml_test.go +++ b/encoding/ghtml/ghtml_test.go @@ -3,6 +3,7 @@ // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. + package ghtml_test import ( diff --git a/go.mod b/go.mod index 804bd0e56..b3709c110 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/go-sql-driver/mysql v1.5.0 github.com/gomodule/redigo v2.0.0+incompatible github.com/gorilla/websocket v1.4.1 - github.com/gqcn/structs v1.1.1 github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf github.com/json-iterator/go v1.1.10 github.com/mattn/go-runewidth v0.0.9 // indirect diff --git a/internal/structs/structs.go b/internal/structs/structs.go index 7cc95c1da..8c30a6bfa 100644 --- a/internal/structs/structs.go +++ b/internal/structs/structs.go @@ -5,14 +5,46 @@ // You can obtain one at https://github.com/gogf/gf. // Package structs provides functions for struct conversion. +// +// Inspired and improved from: https://github.com/fatih/structs package structs -import "github.com/gqcn/structs" +import ( + "reflect" +) -// Field is alias of structs.Field. +// Field contains information of a struct field . type Field struct { - *structs.Field + value reflect.Value + field reflect.StructField // Retrieved tag name. There might be more than one tags in the field, // but only one can be retrieved according to calling function rules. - Tag string + CurrentTag string +} + +// Tag returns the value associated with key in the tag string. If there is no +// such key in the tag, Tag returns the empty string. +func (f *Field) Tag(key string) string { + return f.field.Tag.Get(key) +} + +// Value returns the underlying value of the field. It panics if the field +// is not exported. +func (f *Field) Value() interface{} { + return f.value.Interface() +} + +// IsEmbedded returns true if the given field is an anonymous field (embedded) +func (f *Field) IsEmbedded() bool { + return f.field.Anonymous +} + +// IsExported returns true if the given field is exported. +func (f *Field) IsExported() bool { + return f.field.PkgPath == "" +} + +// Name returns the name of the given field +func (f *Field) Name() string { + return f.field.Name } diff --git a/internal/structs/structs_map.go b/internal/structs/structs_map.go index a029aa647..9f9a4a18b 100644 --- a/internal/structs/structs_map.go +++ b/internal/structs/structs_map.go @@ -6,12 +6,6 @@ package structs -import ( - "reflect" - - "github.com/gqcn/structs" -) - // MapField retrieves struct field as map[name/tag]*Field from , and returns the map. // // The parameter should be type of struct/*struct. @@ -21,75 +15,18 @@ import ( // 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 MapField(pointer interface{}, priority []string, recursive bool) map[string]*Field { - // If points to an invalid address, for example a nil variable, - // it here creates an empty struct using reflect feature. - var ( - tempValue reflect.Value - pointerValue = reflect.ValueOf(pointer) - ) - for pointerValue.Kind() == reflect.Ptr { - tempValue = pointerValue.Elem() - if !tempValue.IsValid() { - pointer = reflect.New(pointerValue.Type().Elem()).Elem() - break - } else { - pointerValue = tempValue +func MapField(pointer interface{}, priority []string) (map[string]*Field, error) { + tagFields, err := getFieldValuesByTagPriority(pointer, priority, map[string]struct{}{}) + if err != nil { + return nil, err + } + tagFieldMap := make(map[string]*Field, len(tagFields)) + for _, field := range tagFields { + tagField := field + tagFieldMap[field.Name()] = tagField + if tagField.CurrentTag != "" { + tagFieldMap[tagField.CurrentTag] = tagField } } - var ( - fields []*structs.Field - fieldMap = make(map[string]*Field) - ) - if v, ok := pointer.(reflect.Value); ok { - fields = structs.Fields(v.Interface()) - } else { - fields = structs.Fields(pointer) - } - var ( - tag = "" - name = "" - ) - for _, field := range fields { - name = field.Name() - // Only retrieve exported attributes. - if name[0] < byte('A') || name[0] > byte('Z') { - continue - } - fieldMap[name] = &Field{ - Field: field, - Tag: tag, - } - tag = "" - for _, p := range priority { - tag = field.Tag(p) - if tag != "" { - break - } - } - if tag != "" { - fieldMap[tag] = &Field{ - Field: field, - Tag: tag, - } - } - if recursive { - var ( - rv = reflect.ValueOf(field.Value()) - kind = rv.Kind() - ) - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - if kind == reflect.Struct { - for k, v := range MapField(rv, priority, true) { - if _, ok := fieldMap[k]; !ok { - fieldMap[k] = v - } - } - } - } - } - return fieldMap + return tagFieldMap, nil } diff --git a/internal/structs/structs_tag.go b/internal/structs/structs_tag.go index a15c688ae..4b8ee5a8a 100644 --- a/internal/structs/structs_tag.go +++ b/internal/structs/structs_tag.go @@ -7,9 +7,8 @@ package structs import ( + "errors" "reflect" - - "github.com/gqcn/structs" ) // TagFields retrieves struct tags as []*Field from , and returns it. @@ -17,94 +16,105 @@ import ( // The parameter should be type of struct/*struct. // // Note that it only retrieves the exported attributes with first letter up-case from struct. -func TagFields(pointer interface{}, priority []string) []*Field { - return doTagFields(pointer, priority, map[string]struct{}{}) +func TagFields(pointer interface{}, priority []string) ([]*Field, error) { + return getFieldValuesByTagPriority(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. -// TODO remove third-party package "structs" by reducing the reflect usage to improve the performance. -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. +func getFieldValues(value interface{}) ([]*Field, error) { var ( - tempValue reflect.Value - pointerValue = reflect.ValueOf(pointer) + reflectValue reflect.Value + reflectKind reflect.Kind ) - for pointerValue.Kind() == reflect.Ptr { - tempValue = pointerValue.Elem() - if !tempValue.IsValid() { - pointer = reflect.New(pointerValue.Type().Elem()).Elem() - break - } else { - pointerValue = tempValue - } - } - var fields []*structs.Field - if v, ok := pointer.(reflect.Value); ok { - fields = structs.Fields(v.Interface()) + if v, ok := value.(reflect.Value); ok { + reflectValue = v + reflectKind = reflectValue.Kind() } else { - var ( - rv = reflect.ValueOf(pointer) - kind = rv.Kind() - ) - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - // If pointer is type of **struct and nil, then automatically create a temporary struct, - // which is used for structs.Fields. - if kind == reflect.Ptr && (!rv.IsValid() || rv.IsNil()) { - fields = structs.Fields(reflect.New(rv.Type().Elem()).Elem().Interface()) + reflectValue = reflect.ValueOf(value) + reflectKind = reflectValue.Kind() + } + + if reflectKind == reflect.Ptr { + if !reflectValue.IsValid() || reflectValue.IsNil() { + // If pointer is type of *struct and nil, then automatically create a temporary struct. + reflectValue = reflect.New(reflectValue.Type().Elem()).Elem() + reflectKind = reflectValue.Kind() } else { - fields = structs.Fields(pointer) + // If pointer is type of **struct and nil, then automatically create a temporary struct. + var ( + pointedValue = reflectValue.Elem() + pointedValueKind = pointedValue.Kind() + ) + if pointedValueKind == reflect.Ptr && (!pointedValue.IsValid() || pointedValue.IsNil()) { + reflectValue = reflect.New(pointedValue.Type().Elem()).Elem() + reflectKind = reflectValue.Kind() + } else { + reflectValue = pointedValue + reflectKind = pointedValueKind + } } } + + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + if reflectKind != reflect.Struct { + return nil, errors.New("given value should be type of struct/*struct") + } var ( - tag = "" - name = "" + structType = reflectValue.Type() + length = reflectValue.NumField() + fields = make([]*Field, length) + ) + for i := 0; i < length; i++ { + fields[i] = &Field{ + value: reflectValue.Field(i), + field: structType.Field(i), + } + } + return fields, nil +} + +func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap map[string]struct{}) ([]*Field, error) { + fields, err := getFieldValues(pointer) + if err != nil { + return nil, err + } + var ( + tagName = "" + tagFields = make([]*Field, 0) ) - tagFields := make([]*Field, 0) for _, field := range fields { - name = field.Name() // Only retrieve exported attributes. - if name[0] < byte('A') || name[0] > byte('Z') { + if !field.IsExported() { continue } - tag = "" + tagName = "" for _, p := range priority { - tag = field.Tag(p) - if tag != "" { + tagName = field.Tag(p) + if tagName != "" && tagName != "-" { break } } - if tag != "" { + if tagName != "" { // Filter repeated tag. - if _, ok := tagMap[tag]; ok { + if _, ok := tagMap[tagName]; ok { continue } - tagFields = append(tagFields, &Field{ - Field: field, - Tag: tag, - }) + tagField := field + tagField.CurrentTag = tagName + tagFields = append(tagFields, tagField) } // If this is an embedded attribute, it retrieves the tags recursively. if field.IsEmbedded() { - var ( - rv = reflect.ValueOf(field.Value()) - kind = rv.Kind() - ) - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - if kind == reflect.Struct { - tagFields = append(tagFields, doTagFields(rv, priority, tagMap)...) + if subTagFields, err := getFieldValuesByTagPriority(field.value, priority, tagMap); err != nil { + return nil, err + } else { + tagFields = append(tagFields, subTagFields...) } } } - return tagFields + return tagFields, nil } // TagMapName retrieves struct tags as map[tag]attribute from , and returns it. @@ -112,13 +122,16 @@ func doTagFields(pointer interface{}, priority []string, tagMap map[string]struc // The parameter should be type of struct/*struct. // // Note that it only retrieves the exported attributes with first letter up-case from struct. -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() +func TagMapName(pointer interface{}, priority []string) (map[string]string, error) { + fields, err := TagFields(pointer, priority) + if err != nil { + return nil, err } - return tagMap + tagMap := make(map[string]string, len(fields)) + for _, field := range fields { + tagMap[field.CurrentTag] = field.Name() + } + return tagMap, nil } // TagMapField retrieves struct tags as map[tag]*Field from , and returns it. @@ -126,11 +139,15 @@ func TagMapName(pointer interface{}, priority []string) map[string]string { // The parameter should be type of struct/*struct. // // Note that it only retrieves the exported attributes with first letter up-case from struct. -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 +func TagMapField(pointer interface{}, priority []string) (map[string]*Field, error) { + fields, err := TagFields(pointer, priority) + if err != nil { + return nil, err } - return tagMap + tagMap := make(map[string]*Field, len(fields)) + for _, field := range fields { + tagField := field + tagMap[field.CurrentTag] = tagField + } + return tagMap, nil } diff --git a/internal/structs/structs_test.go b/internal/structs/structs_test.go index f1840d62b..19a864ce1 100644 --- a/internal/structs/structs_test.go +++ b/internal/structs/structs_test.go @@ -24,12 +24,17 @@ 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"}), g.Map{"name": "Name", "pass": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"}) + m, _ := structs.TagMapName(user, []string{"params"}) + t.Assert(m, g.Map{"name": "Name", "pass": "Pass"}) + m, _ = structs.TagMapName(&user, []string{"params"}) + t.Assert(m, g.Map{"name": "Name", "pass": "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"}) + m, _ = structs.TagMapName(&user, []string{"params", "my-tag1"}) + t.Assert(m, g.Map{"name": "Name", "pass": "Pass"}) + m, _ = structs.TagMapName(&user, []string{"my-tag1", "params"}) + t.Assert(m, g.Map{"name": "Name", "pass1": "Pass"}) + m, _ = structs.TagMapName(&user, []string{"my-tag2", "params"}) + t.Assert(m, g.Map{"name": "Name", "pass2": "Pass"}) }) gtest.C(t, func(t *gtest.T) { @@ -43,7 +48,8 @@ func Test_Basic(t *testing.T) { Base `params:"base"` } user := new(UserWithBase) - t.Assert(structs.TagMapName(user, []string{"params"}), g.Map{ + m, _ := structs.TagMapName(user, []string{"params"}) + t.Assert(m, g.Map{ "base": "Base", "password1": "Pass1", "password2": "Pass2", @@ -67,8 +73,10 @@ func Test_Basic(t *testing.T) { } 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{}) + m, _ := structs.TagMapName(user1, []string{"params"}) + t.Assert(m, g.Map{"password1": "Pass1", "password2": "Pass2"}) + m, _ = structs.TagMapName(user2, []string{"params"}) + t.Assert(m, g.Map{}) }) } @@ -80,11 +88,16 @@ 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"}), g.Map{"name": "Name", "pass": "Pass"}) - t.Assert(structs.TagMapName(&user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"}) + m, _ := structs.TagMapName(user, []string{"params"}) + t.Assert(m, g.Map{"name": "Name", "pass": "Pass"}) + m, _ = structs.TagMapName(&user, []string{"params"}) + t.Assert(m, g.Map{"name": "Name", "pass": "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"}) + m, _ = structs.TagMapName(&user, []string{"params", "my-tag1"}) + t.Assert(m, g.Map{"name": "Name", "pass": "Pass"}) + m, _ = structs.TagMapName(&user, []string{"my-tag1", "params"}) + t.Assert(m, g.Map{"name": "Name", "pass1": "Pass"}) + m, _ = structs.TagMapName(&user, []string{"my-tag2", "params"}) + t.Assert(m, g.Map{"name": "Name", "pass2": "Pass"}) }) } diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index bb5068807..44df0d092 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -11,10 +11,10 @@ import ( "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/internal/structs" "reflect" "strings" - "github.com/gogf/gf/internal/structs" "github.com/gogf/gf/internal/utils" ) @@ -196,7 +196,11 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str // 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) { + tagToNameMap, err := structs.TagMapName(elem, StructTagPriority) + if err != nil { + return err + } + for k, v := range tagToNameMap { tagMap[v] = utils.RemoveSymbols(k) } @@ -263,8 +267,7 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map } defer func() { if e := recover(); e != nil { - err = bindVarToReflectValue(structFieldValue, value, mapping...) - if err != nil { + if err = bindVarToReflectValue(structFieldValue, value, mapping...); err != nil { err = gerror.Wrapf(err, `error binding value to attribute "%s"`, name) } } diff --git a/util/gvalid/gvalid_check_struct.go b/util/gvalid/gvalid_check_struct.go index 6d2d4e67a..31891fe76 100644 --- a/util/gvalid/gvalid_check_struct.go +++ b/util/gvalid/gvalid_check_struct.go @@ -73,16 +73,24 @@ func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) * checkRules = v } // Checks and extends the parameters map with struct alias tag. - for nameOrTag, field := range structs.MapField(object, aliasNameTagPriority, true) { + mapField, err := structs.MapField(object, aliasNameTagPriority) + if err != nil { + return newErrorStr("invalid_object", err.Error()) + } + for nameOrTag, field := range mapField { params[nameOrTag] = field.Value() 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) { + tagField, err := structs.TagFields(object, structTagPriority) + if err != nil { + return newErrorStr("invalid_object", err.Error()) + } + for _, field := range tagField { fieldName := field.Name() // sequence tag == struct tag // The name here is alias of field name. - name, rule, msg := parseSequenceTag(field.Tag) + name, rule, msg := parseSequenceTag(field.CurrentTag) if len(name) == 0 { name = fieldName } else {