https://mp.weixin.qq.com/s/4FmxImNLcU0-up5aVZLMzw
1 介绍
通常情况下,结构体标签被用于提供结构体字段如何被编码为或者解码自另外一种格式的转换信息(或者是以何种形式被保存至/获取自数据库)。不过,你也可以用它存储任何你想要设置的”元信息“,供其他包或者自己使用。
2 使用规范
结构体标签字符串的值是一个由空格分隔的 key:”value” 对列表
- 键,通常表示后面跟的“值”是被哪个包使用的,例如json这个键会被encoding/json包处理使用。多个键用空格分隔
- 在“键”对应的“值”中传递多个信息,通常通过用,分隔来指定
type User struct {
Name string `json:"name" xml:"name"`
}
如果一个字段的结构体标签里某个键的“值”被设置成了的破折号 (‘-‘),那么就意味着告诉处理该结构体标签键值的进程排除该字段。就以为进行JSON编码/解码时忽略Name这个字段。
Name string `json:"-"`
3 通过反射获取 自定义的结构体标签
```go // Get方法解析标签的值并返回你指定的键的“值”。 func (tag StructTag) Get(key string) string
// Lookup会通过返回的ok值告知给定key是否存在与标签中。 func (tag StructTag) Lookup(key string) (value string, ok bool)
```go
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `mytag:"MyName"`
Email string `mytag:"MyEmail"`
}
func main() {
u := User{"Bob", "bob@mycompany.com"}
t := reflect.TypeOf(u)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field: User.%s\n", field.Name)
fmt.Printf("\tWhole tag value : %s\n", field.Tag)
fmt.Printf("\tValue of 'mytag': %s\n", field.Tag.Get("mytag"))
}
}
上面的程序会输出
Field: User.Name
Whole tag value : mytag:"MyName"
Value of 'mytag': MyName
Field: User.Email
Whole tag value : mytag:"MyEmail"
Value of 'mytag': MyEmail
4 常用的结构体标签key
常用的结构体标签Key,指的是那些被一些常用的开源包声明使用的结构体标签键。在这里总结了一些,都是一些我们平时会用到的包,它们是:
- json: 由encoding/json 包使用,详见json.Marshal()的使用方法和实现逻辑。
- xml : 由encoding/xml包使用,详见xml.Marshal()。
- bson: 由gobson包,和mongo-go包使用。
- protobuf: 由github.com/golang/protobuf/proto 使用,在包文档中有详细说明。
- yaml: 由gopkg.in/yaml.v2 包使用,详见yaml.Marshal()。
- gorm: 由gorm.io/gorm包使用,示例可以在GORM的文档中找到。
BSON()是一种类json的一种二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和BinData类型。 bson是由10gen开发的一个数据格式,目前主要用于mongoDB中
5 常用的结构体标签val
(1) omitempty
在执行json.Marshal进行序列化时会将payment字段给过滤掉,这就是omitempty的作用会在序列化时过滤掉false、 0、空指针、空接口、空数组、空切片、空映射、空字符串。
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Payment float64 `json:"payment,omitempty" `
}
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Payment float64 `json:"payment,omitempty" `
}
func main() {
var chaochao Person
file, err := os.Open("student.json")
if err != nil {
panic(err)
}
defer file.Close()
content, err := ioutil.ReadAll(file)
json.Unmarshal(content, &chaochao)
fmt.Println(chaochao)
data, _ := json.Marshal(&chaochao)
fmt.Println(string(data))
}
输出结果
{chaochao 23 0}
{"name":"chaochao","age":23}
(2) required
默认参数是可选, 不传则为零值, 加上required后参数为必传
type Info struct {
Name string `json:"-"` // 告诉编码器完全跳过该字段。
Sex string `json:"sex,required"`
}
(3) min max
type Info struct {
Name string `json:"-"`
Age int `json:"age,min=17,max=60"`
}
(4) 类型转化
type Info struct {
Name string
Age int `json:"age,string"`
//这样生成的json对象中,age就为字符串
Sex string
}
6 自定义结构体标签
package main
import (
"fmt"
"reflect"
"regexp"
"strings"
)
//Name of the struct tag used in example.
const tagName = "validate"
//Regular expression to validate email address.
var mailRe = regexp.MustCompile(`\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z`)
//Generic data validator
type Validator interface {
//Validate method performs validation and returns results and optional error.
Validate(interface{}) (bool, error)
}
//DefaultValidator does not perform any validations
type DefaultValidator struct {
}
func (v DefaultValidator) Validate(val interface{}) (bool, error) {
return true, nil
}
type NumberValidator struct {
Min int
Max int
}
func (v NumberValidator) Validate(val interface{}) (bool, error) {
num := val.(int)
if num < v.Min {
return false, fmt.Errorf("should be greater than %v", v.Min)
}
if v.Max >= v.Min && num > v.Max {
return false, fmt.Errorf("should be less than %v", v.Max)
}
return true, nil
}
//StringValidator validates string presence and/or its length
type StringValidator struct {
Min int
Max int
}
func (v StringValidator) Validate(val interface{}) (bool, error) {
l := len(val.(string))
if l == 0 {
return false, fmt.Errorf("cannot be blank")
}
if l < v.Min {
return false, fmt.Errorf("should be at least %v chars long", v.Min)
}
if v.Max >= v.Min && l > v.Max {
return false, fmt.Errorf("should be less than %v chars long", v.Max)
}
return true, nil
}
type EmailValidator struct {
}
func (v EmailValidator) Validate(val interface{}) (bool, error) {
if !mailRe.MatchString(val.(string)) {
return false, fmt.Errorf("is not a valid email address")
}
return true, nil
}
//Returns validator struct corresponding to validation type
func getValidatorFromTag(tag string) Validator {
args := strings.Split(tag, ",")
switch args[0] {
case "number":
validator := NumberValidator{}
fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)
return validator
case "string":
validator := StringValidator{}
fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)
return validator
case "email":
return EmailValidator{}
}
return DefaultValidator{}
}
//Performs actual data validation using validator definitions on the struct
func validateStruct(s interface{}) []error {
errs := []error{}
//ValueOf returns a Value representing the run-time data
v := reflect.ValueOf(s)
for i := 0; i < v.NumField(); i++ {
//Get the field tag value
tag := v.Type().Field(i).Tag.Get(tagName)
//Skip if tag is not defined or ignored
if tag == "" || tag == "-" {
continue
}
//Get a validator that corresponds to a tag
validator := getValidatorFromTag(tag)
//Perform validation
valid, err := validator.Validate(v.Field(i).Interface())
//Append error to results
if !valid && err != nil {
errs = append(errs, fmt.Errorf("%s %s", v.Type().Field(i).Name, err.Error()))
}
}
return errs
}
type User struct {
Id int `validate:"number,min=1,max=1000"`
Name string `validate:"string,min=2,max=10"`
Bio string `validate:"string"`
Email string `validate:"string"`
}
func main() {
user := User{
Id: 0,
Name: "superlongstring",
Bio: "",
Email: "foobar",
}
fmt.Println("Errors: ")
for i, err := range validateStruct(user) {
fmt.Printf("\t%d. %s\n", i+1, err.Error())
}
}