• Go语言中如何定制结构标签?
  • 发布于 2个月前
  • 186 热度
    0 评论
  • BruceLe
  • 1 粉丝 37 篇博客
  •   
在 Go 中,可以使用结构标签为结构字段添加自定义元数据。这些标签可以用于如在将结构转换为 JSON 或 XML 等格式时指定字段名等事情。它们还允许更高级的选项,如 omitempty 选项。通过使用反射,我们可以访问这些结构标签,并使用它们来定制我们的代码行为。让我们深入研究。

一. 解释验证示例
现在,让我们看看下面的示例,了解一下我们将在本文中学到什么:
// 堆代码 duidaima.com
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 的结构体。该结构体有一个叫做 "Age" 的字段,该字段有一个结构标签 min=18,将最小年龄限制设定为 18 岁。
该结构体还有一个名为 "Name" 的字段,该字段有一个结构标签 validate:"required",这意味着该字段不能为空。

我们在本文的目标是创建一个可以理解这些结构标签并基于这些规则验证学生对象是否有效的验证函数。具体来说,我们将创建一个 Validate(any) 函数,用来检查学生的年龄是否≥18岁,以及姓名字段是否不为空。

二. 验证函数
1. 在我们可以验证学生结构体之前,我们需要先将它转换成一个 reflect.Value 对象。如果你对这个过程不熟悉,你可能想看看我之前关于反射的文章。
// 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))
}
为了将学生结构体转换为 reflect.Value 对象,我们使用 reflect.ValueOf(student) 方法。然而,由于我们不知道传入的参数 s 是指针、接口还是简单的结构体,我们使用 reflect.Indirect() 方法来确保我们得到结构体的正确值。

2. 接下来,我们将遍历 Student 结构体的所有字段,以便检索结构标签 “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
3. 一旦我们得到了验证代码,剩下的任务就是处理和应用它。
// 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
  }
 }
}
4. (可选)如果你对我如何处理 isRequired 和 isMin 验证规则感到好奇,请继续阅读。
• 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())
}
三. 使用包
本文使用验证作为一个例子来解释自定义结构标签的概念。然而,在现实世界的项目中,并不需要重新发明轮子。
“自定义结构标签是不是不实用?”
使用自定义结构标签肯定是实用的,正如我们在本文中所展示的。然而,当涉及到验证时,考虑使用现有的验证库是值得的,因为它们通常具有更广泛的功能,并且更好地处理边缘情况。

一个很好的例子就是“validator”包,它具有一系列内置函数,如 required 和 min,可以在验证过程中使用,正如我们在上面的示例中所看到的。


总结
总的来说,使用 Go 中的自定义结构标签是一种强大的技术,它使得以灵活的方式为你的结构字段添加验证规则变得容易。
用户评论