Golang 技巧:定制結(jié)構(gòu)標(biāo)簽,如 `json:”name”`

學(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ù)

  1. 在我們可以驗證學(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)體的正確值。

  1. 接下來,我們將遍歷 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
  1. 一旦我們得到了驗證代碼,剩下的任務(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
  }
 }
}
  1. (可選)如果你對我如何處理 isRequiredisMin 驗證規(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ù),如 requiredmin,可以在驗證過程中使用,正如我們在上面的示例中所看到的。

結(jié)束這一章節(jié),開始新的篇章

總的來說,使用 Go 中的自定義結(jié)構(gòu)標(biāo)簽是一種強大的技術(shù),它使得以靈活的方式為你的結(jié)構(gòu)字段添加驗證規(guī)則變得容易。

保持學(xué)習(xí)并從中找到樂趣,愉快的編碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容