學(xué)習(xí)如何在 Go 中創(chuàng)建自定義結(jié)構(gòu)標(biāo)簽,類似于廣泛使用的
json:"name"標(biāo)簽。

在 Go 中,可以使用結(jié)構(gòu)標(biāo)簽為結(jié)構(gòu)字段添加自定義元數(shù)據(jù)。
這些標(biāo)簽可以用于如在將結(jié)構(gòu)轉(zhuǎn)換為 JSON 或 XML 等格式時指定字段名等事情。它們還允許更高級的選項,如 omitempty 選項。
通過使用反射,我們可以訪問這些結(jié)構(gòu)標(biāo)簽,并使用它們來定制我們的代碼行為。
讓我們深入研究。
1. 解釋驗證示例
現(xiàn)在,讓我們看看下面的示例,了解一下我們將在本文中學(xué)到什么:
type Student struct {
Age int `validate:"min=18"`
Name string `validate:"required"`
}
func main() {
s := Student{Age: 90, Name: "John"}
err := Validate(s)
if err != nil {
fmt.Println(err)
}
}
在這個示例中,我們有一個名為 Student 的結(jié)構(gòu)體。該結(jié)構(gòu)體有一個叫做 "Age" 的字段,該字段有一個結(jié)構(gòu)標(biāo)簽 min=18,將最小年齡限制設(shè)定為 18 歲。
該結(jié)構(gòu)體還有一個名為 "Name" 的字段,該字段有一個結(jié)構(gòu)標(biāo)簽 validate:"required",這意味著該字段不能為空。
我們在本文的目標(biāo)是創(chuàng)建一個可以理解這些結(jié)構(gòu)標(biāo)簽并基于這些規(guī)則驗證學(xué)生對象是否有效的驗證函數(shù)。具體來說,我們將創(chuàng)建一個 Validate(any) 函數(shù),用來檢查學(xué)生的年齡是否≥18歲,以及姓名字段是否不為空。
2. 驗證函數(shù)
- 在我們可以驗證學(xué)生結(jié)構(gòu)體之前,我們需要先將它轉(zhuǎn)換成一個
reflect.Value對象。如果你對這個過程不熟悉,你可能想看看我之前關(guān)于反射的文章。
// create a student which violates age validation
student := Student{Age: 17, Name: "Aiden"}
func Validate(s interface{}) {
// get the value of interface{}/ pointer point to
val := reflect.Indirect(reflect.ValueOf(s))
}
為了將學(xué)生結(jié)構(gòu)體轉(zhuǎn)換為 reflect.Value 對象,我們使用 reflect.ValueOf(student) 方法。
然而,由于我們不知道傳入的參數(shù) s 是指針、接口還是簡單的結(jié)構(gòu)體,我們使用 reflect.Indirect() 方法來確保我們得到結(jié)構(gòu)體的正確值。
- 接下來,我們將遍歷 Student 結(jié)構(gòu)體的所有字段,以便檢索結(jié)構(gòu)標(biāo)簽
“validate”的值。
for i := 0; i < val.NumField(); i++ {
typeField := val.Type().Field(i) // get field i-th of type(val)
tag := typeField.Tag.Get("validate")
if tag == "" {
continue
}
fmt.Println(tag)
}
// min=18
// required
- 一旦我們得到了驗證代碼,剩下的任務(wù)就是處理和應(yīng)用它。
// get the value of field like 17 or "Aiden"
valueField := val.Field(i)
// split the tag so we can use like this: `required:"limit=20"
rules := strings.Split(tag, ",")
for _, rule := range rules {
parts := strings.Split(rule, "=")
key := parts[0]
var value string
if len(parts) > 1 {
value = parts[1]
}
switch key {
case "required":
if err := isRequired(valueField); err != nil {
return err
}
case "min":
if err := isMin(valueField, value); err != nil {
return err
}
}
}
- (可選)如果你對我如何處理
isRequired和isMin驗證規(guī)則感到好奇,請繼續(xù)閱讀。
- isMin 驗證:
func isMin(valueField reflect.Value, minStr string) error {
typeField := valueField.Type()
if minStr == "" {
return nil
}
min, err := strconv.ParseFloat(minStr, 64)
if err != nil {
return fmt.Errorf("min value %f is not a number", min)
}
switch valueField.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if float64(valueField.Int()) < min {
return fmt.Errorf("field %s must be greater or equal %d", typeField.Name(), int(min))
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if float64(valueField.Uint()) < min {
return fmt.Errorf("field %s must be greater or equal than %d", typeField.Name(), uint(min))
}
case reflect.Float32, reflect.Float64:
if valueField.Float() < min {
return fmt.Errorf("field %s must be greater or equal than %f", typeField.Name(), min)
}
}
return nil
}
- isRequired 驗證:
func isRequired(v reflect.Value) error {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
if v.Len() != 0 {
return nil
}
case reflect.Bool:
if v.Bool() {
return nil
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.Int() != 0 {
return nil
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if v.Uint() != 0 {
return nil
}
}
return fmt.Errorf("field %s is required", v.Type().Name())
}
3. 使用包
本文使用驗證作為一個例子來解釋自定義結(jié)構(gòu)標(biāo)簽的概念。然而,在現(xiàn)實世界的項目中,并不需要重新發(fā)明輪子。
“自定義結(jié)構(gòu)標(biāo)簽是不是不實用?”
使用自定義結(jié)構(gòu)標(biāo)簽肯定是實用的,正如我們在本文中所展示的。
然而,當(dāng)涉及到驗證時,考慮使用現(xiàn)有的驗證庫是值得的,因為它們通常具有更廣泛的功能,并且更好地處理邊緣情況。
一個很好的例子就是“validator”包,它具有一系列內(nèi)置函數(shù),如 required 和 min,可以在驗證過程中使用,正如我們在上面的示例中所看到的。
結(jié)束這一章節(jié),開始新的篇章
總的來說,使用 Go 中的自定義結(jié)構(gòu)標(biāo)簽是一種強大的技術(shù),它使得以靈活的方式為你的結(jié)構(gòu)字段添加驗證規(guī)則變得容易。
保持學(xué)習(xí)并從中找到樂趣,愉快的編碼!