mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
rename internal/structtag to internal/structs; add more feature for internal/structs; improve gvalid to support recursive validation for struct; improve gconv.Struct/Map functions
This commit is contained in:
15
g/internal/structs/structs.go
Normal file
15
g/internal/structs/structs.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// 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 structs provides functions for struct conversion.
|
||||
package structs
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/third/github.com/fatih/structs"
|
||||
)
|
||||
|
||||
// Field is alias of structs.Field.
|
||||
type Field = structs.Field
|
||||
@ -4,19 +4,21 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package util provides util functions for internal usage.
|
||||
package structtag
|
||||
package structs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/third/github.com/fatih/structs"
|
||||
)
|
||||
|
||||
// Map recursively retrieves struct tags as map[tag]attribute from <pointer>, and returns it.
|
||||
func Map(pointer interface{}, priority []string) map[string]string {
|
||||
tagMap := make(map[string]string)
|
||||
// MapField retrieves struct field as map[name/tag]*Field from <pointer>, and returns it.
|
||||
//
|
||||
// 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 MapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
|
||||
fieldMap := make(map[string]*Field)
|
||||
fields := ([]*structs.Field)(nil)
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
fields = structs.Fields(v.Interface())
|
||||
@ -26,6 +28,12 @@ func Map(pointer interface{}, priority []string) map[string]string {
|
||||
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
|
||||
tag = ""
|
||||
for _, p := range priority {
|
||||
tag = field.Tag(p)
|
||||
@ -33,16 +41,10 @@ func Map(pointer interface{}, priority []string) map[string]string {
|
||||
break
|
||||
}
|
||||
}
|
||||
name = field.Name()
|
||||
// Only retrieve exported attributes.
|
||||
if name[0] < byte('A') || name[0] > byte('Z') {
|
||||
continue
|
||||
}
|
||||
if tag != "" {
|
||||
for _, v := range strings.Split(tag, ",") {
|
||||
tagMap[strings.TrimSpace(v)] = name
|
||||
}
|
||||
} else {
|
||||
fieldMap[tag] = field
|
||||
}
|
||||
if recursive {
|
||||
rv := reflect.ValueOf(field.Value())
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
@ -50,13 +52,13 @@ func Map(pointer interface{}, priority []string) map[string]string {
|
||||
kind = rv.Kind()
|
||||
}
|
||||
if kind == reflect.Struct {
|
||||
for k, v := range Map(rv, priority) {
|
||||
if _, ok := tagMap[k]; !ok {
|
||||
tagMap[k] = v
|
||||
for k, v := range MapField(rv, priority, true) {
|
||||
if _, ok := fieldMap[k]; !ok {
|
||||
fieldMap[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tagMap
|
||||
return fieldMap
|
||||
}
|
||||
81
g/internal/structs/structs_tag.go
Normal file
81
g/internal/structs/structs_tag.go
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// 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 structs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/third/github.com/fatih/structs"
|
||||
)
|
||||
|
||||
// TagMapName retrieves struct tags as map[tag]attribute from <pointer>, and returns it.
|
||||
//
|
||||
// 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 {
|
||||
tagMap := TagMapField(pointer, priority, recursive)
|
||||
if len(tagMap) > 0 {
|
||||
m := make(map[string]string, len(tagMap))
|
||||
for k, v := range tagMap {
|
||||
m[k] = v.Name()
|
||||
}
|
||||
return m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TagMapField retrieves struct tags as map[tag]*Field from <pointer>, and returns it.
|
||||
//
|
||||
// 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 {
|
||||
tagMap := make(map[string]*Field)
|
||||
fields := ([]*structs.Field)(nil)
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
fields = structs.Fields(v.Interface())
|
||||
} else {
|
||||
fields = structs.Fields(pointer)
|
||||
}
|
||||
tag := ""
|
||||
name := ""
|
||||
for _, field := range fields {
|
||||
name = field.Name()
|
||||
// Only retrieve exported attributes.
|
||||
if name[0] < byte('A') || name[0] > byte('Z') {
|
||||
continue
|
||||
}
|
||||
|
||||
tag = ""
|
||||
for _, p := range priority {
|
||||
tag = field.Tag(p)
|
||||
if tag != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tag != "" {
|
||||
tagMap[tag] = field
|
||||
}
|
||||
if recursive {
|
||||
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 TagMapField(rv, priority, true) {
|
||||
if _, ok := tagMap[k]; !ok {
|
||||
tagMap[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tagMap
|
||||
}
|
||||
@ -4,14 +4,14 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package structtag_test
|
||||
package structs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/internal/structs"
|
||||
|
||||
"github.com/gogf/gf/g/internal/structtag"
|
||||
"github.com/gogf/gf/g"
|
||||
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
)
|
||||
@ -24,12 +24,12 @@ func Test_Basic(t *testing.T) {
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user User
|
||||
gtest.Assert(structtag.Map(user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"})
|
||||
gtest.Assert(structtag.Map(&user, []string{"params"}), g.Map{"name": "Name", "pass": "Pass"})
|
||||
gtest.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
gtest.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
|
||||
gtest.Assert(structtag.Map(&user, []string{"params", "my-tag1"}), g.Map{"name": "Name", "pass": "Pass"})
|
||||
gtest.Assert(structtag.Map(&user, []string{"my-tag1", "params"}), g.Map{"name": "Name", "pass1": "Pass"})
|
||||
gtest.Assert(structtag.Map(&user, []string{"my-tag2", "params"}), g.Map{"name": "Name", "pass2": "Pass"})
|
||||
gtest.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
gtest.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"})
|
||||
gtest.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"})
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
@ -43,7 +43,11 @@ func Test_Basic(t *testing.T) {
|
||||
Base `params:"base"`
|
||||
}
|
||||
user := new(UserWithBase)
|
||||
gtest.Assert(structtag.Map(user, []string{"params"}), g.Map{"base": "Base"})
|
||||
gtest.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{
|
||||
"base": "Base",
|
||||
"password1": "Pass1",
|
||||
"password2": "Pass2",
|
||||
})
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
@ -63,7 +67,7 @@ func Test_Basic(t *testing.T) {
|
||||
}
|
||||
user1 := new(UserWithBase1)
|
||||
user2 := new(UserWithBase2)
|
||||
gtest.Assert(structtag.Map(user1, []string{"params"}), g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
gtest.Assert(structtag.Map(user2, []string{"params"}), g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
gtest.Assert(structs.TagMapName(user1, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
gtest.Assert(structs.TagMapName(user2, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
})
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/structtag"
|
||||
"github.com/gogf/gf/g/internal/structs"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
@ -144,7 +144,7 @@ func (r *Request) GetPostMap(def ...map[string]string) map[string]string {
|
||||
|
||||
// 将所有的request参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
|
||||
func (r *Request) GetPostToStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
tagMap := structtag.Map(pointer, paramTagPriority)
|
||||
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
tagMap[k] = v
|
||||
|
||||
@ -9,8 +9,7 @@ package ghttp
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/g/internal/structtag"
|
||||
|
||||
"github.com/gogf/gf/g/internal/structs"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
@ -154,7 +153,7 @@ func (r *Request) GetQueryMap(def ...map[string]string) map[string]string {
|
||||
|
||||
// 将所有的get参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
|
||||
func (r *Request) GetQueryToStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
tagmap := structtag.Map(pointer, paramTagPriority)
|
||||
tagmap := structs.TagMapName(pointer, paramTagPriority, true)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
tagmap[k] = v
|
||||
|
||||
@ -8,7 +8,7 @@ package ghttp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/internal/structtag"
|
||||
"github.com/gogf/gf/g/internal/structs"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
@ -134,7 +134,7 @@ func (r *Request) GetRequestMap(def ...map[string]string) map[string]string {
|
||||
|
||||
// 将所有的request参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
|
||||
func (r *Request) GetRequestToStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
tagMap := structtag.Map(pointer, paramTagPriority)
|
||||
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
tagMap[k] = v
|
||||
|
||||
@ -9,10 +9,11 @@ package gconv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/g/encoding/gbinary"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/g/encoding/gbinary"
|
||||
)
|
||||
|
||||
// Type assert api for String().
|
||||
@ -25,6 +26,10 @@ type apiError interface {
|
||||
Error() string
|
||||
}
|
||||
|
||||
const (
|
||||
gGCONV_TAG = "gconv"
|
||||
)
|
||||
|
||||
var (
|
||||
// Empty strings.
|
||||
emptyStringMap = map[string]struct{}{
|
||||
@ -33,6 +38,9 @@ var (
|
||||
"off": struct{}{},
|
||||
"false": struct{}{},
|
||||
}
|
||||
|
||||
// Priority tags for Map*/Struct* functions.
|
||||
structTagPriority = []string{gGCONV_TAG, "json"}
|
||||
)
|
||||
|
||||
// Convert converts the variable <i> to the type <t>, the type <t> is specified by string.
|
||||
|
||||
@ -7,20 +7,19 @@
|
||||
package gconv
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/empty"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
gGCONV_TAG = "gconv"
|
||||
"github.com/gogf/gf/g/internal/empty"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
)
|
||||
|
||||
// Map converts any variable <value> to map[string]interface{}.
|
||||
// If the parameter <value> is not a map type, then the conversion will fail and returns nil.
|
||||
// If <value> is a struct object, the second parameter <tags> specifies the most priority
|
||||
// tags that will be detected, otherwise it detects the tags in order of: gconv, json.
|
||||
//
|
||||
// If the parameter <value> is not a map/struct/*struct type, then the conversion will fail and returns nil.
|
||||
//
|
||||
// If <value> is a struct/*struct object, the second parameter <tags> specifies the most priority
|
||||
// tags that will be detected, otherwise it detects the tags in order of: gconv, json, and then the field name.
|
||||
func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
@ -105,7 +104,7 @@ func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
case reflect.Struct:
|
||||
rt := rv.Type()
|
||||
name := ""
|
||||
tagArray := []string{gGCONV_TAG, "json"}
|
||||
tagArray := structTagPriority
|
||||
switch len(tags) {
|
||||
case 0:
|
||||
// No need handle.
|
||||
@ -114,9 +113,6 @@ func Map(value interface{}, tags ...string) map[string]interface{} {
|
||||
default:
|
||||
tagArray = tags
|
||||
}
|
||||
if gstr.SearchArray(tagArray, gGCONV_TAG) < 0 {
|
||||
tagArray = append(tagArray, gGCONV_TAG)
|
||||
}
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
// Only convert the public attributes.
|
||||
fieldName := rt.Field(i).Name
|
||||
|
||||
@ -12,14 +12,10 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/g/internal/structtag"
|
||||
"github.com/gogf/gf/g/internal/structs"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
)
|
||||
|
||||
var (
|
||||
structTagPriority = []string{"gconv", "json"}
|
||||
)
|
||||
|
||||
// Struct maps the params key-value pairs to the corresponding struct object's properties.
|
||||
// The third parameter <mapping> is unnecessary, indicating the mapping rules between the custom key name
|
||||
// and the attribute name(case sensitive).
|
||||
@ -72,7 +68,7 @@ func Struct(params interface{}, pointer interface{}, mapping ...map[string]strin
|
||||
}
|
||||
}
|
||||
// It secondly checks the tags of attributes.
|
||||
tagMap := structtag.Map(pointer, structTagPriority)
|
||||
tagMap := structs.TagMapName(pointer, structTagPriority, true)
|
||||
for tagK, tagV := range tagMap {
|
||||
if _, ok := doneMap[tagV]; ok {
|
||||
continue
|
||||
|
||||
@ -9,15 +9,19 @@ package gvalid
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"github.com/gogf/gf/g/internal/structs"
|
||||
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/third/github.com/fatih/structs"
|
||||
)
|
||||
|
||||
var (
|
||||
// 同时支持valid和gvalid标签,优先使用valid
|
||||
structTagPriority = []string{"valid", "gvalid"}
|
||||
)
|
||||
|
||||
// 校验struct对象属性,object参数也可以是一个指向对象的指针,返回值同CheckMap方法。
|
||||
// struct的数据校验结果信息是顺序的。
|
||||
func CheckStruct(object interface{}, rules interface{}, msgs ...CustomMsg) *Error {
|
||||
fields := structs.Fields(object)
|
||||
params := make(map[string]interface{})
|
||||
checkRules := make(map[string]string)
|
||||
customMsgs := make(CustomMsg)
|
||||
@ -57,26 +61,25 @@ func CheckStruct(object interface{}, rules interface{}, msgs ...CustomMsg) *Erro
|
||||
errorRules = append(errorRules, name+"@"+rule)
|
||||
}
|
||||
|
||||
// 不支持校验错误顺序: map[键名]校验规则
|
||||
// 不支持校验错误顺序: map[键名]校验规则
|
||||
case map[string]string:
|
||||
checkRules = v
|
||||
}
|
||||
// 首先, 按照属性循环一遍将struct的属性、数值、tag解析
|
||||
for _, field := range fields {
|
||||
tagValue := ""
|
||||
for _, field := range structs.MapField(object, structTagPriority, true) {
|
||||
fieldName := field.Name()
|
||||
// 只检测公开属性
|
||||
if !gstr.IsLetterUpper(fieldName[0]) {
|
||||
continue
|
||||
}
|
||||
params[fieldName] = field.Value()
|
||||
// 同时支持valid和gvalid标签,优先使用valid
|
||||
tag := field.Tag("valid")
|
||||
if tag == "" {
|
||||
tag = field.Tag("gvalid")
|
||||
tagValue = ""
|
||||
for _, v := range structTagPriority {
|
||||
tagValue = field.Tag(v)
|
||||
if tagValue != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tag != "" {
|
||||
if tagValue != "" {
|
||||
// sequence tag == struct tag, 这里的name为别名
|
||||
name, rule, msg := parseSequenceTag(tag)
|
||||
name, rule, msg := parseSequenceTag(tagValue)
|
||||
if len(name) == 0 {
|
||||
name = fieldName
|
||||
}
|
||||
|
||||
@ -4,18 +4,16 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// 返回错误对象。
|
||||
|
||||
package gvalid
|
||||
|
||||
import "strings"
|
||||
|
||||
// 校验错误对象
|
||||
type Error struct {
|
||||
rules []string // 校验结果顺序(可能为nil)
|
||||
errors ErrorMap // 校验结果(map无序)
|
||||
firstKey string // 第一条错误项键名(常用操作冗余数据)
|
||||
firstItem map[string]string // 第一条错误项(常用操作冗余数据)
|
||||
rules []string // 校验结果顺序(可能为nil),可保证返回校验错误的顺序性
|
||||
errors ErrorMap // 完整的数据校验结果存储(map无序)
|
||||
firstKey string // 第一条错误项键名(常用操作冗余数据),默认为空
|
||||
firstItem map[string]string // 第一条错误项(常用操作冗余数据),默认为nil
|
||||
}
|
||||
|
||||
// 校验错误信息: map[键名]map[规则名]错误信息
|
||||
|
||||
@ -9,6 +9,8 @@ package gvalid_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/g"
|
||||
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/gvalid"
|
||||
)
|
||||
@ -90,3 +92,29 @@ func Test_CheckStruct(t *testing.T) {
|
||||
gtest.Assert(err.Maps()["uid"]["min"], "ID不能为空")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_With_Inherit(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type Pass struct {
|
||||
Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"`
|
||||
Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `valid:"name@required#请输入您的姓名"`
|
||||
Pass Pass
|
||||
}
|
||||
user := &User{
|
||||
Name: "",
|
||||
Pass: Pass{
|
||||
Pass1: "1",
|
||||
Pass2: "2",
|
||||
},
|
||||
}
|
||||
err := gvalid.CheckStruct(user, nil)
|
||||
gtest.AssertNE(err, nil)
|
||||
gtest.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"})
|
||||
gtest.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"})
|
||||
gtest.Assert(err.Maps()["password2"], g.Map{"same": "您两次输入的密码不一致"})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user