一尘不染

遍历Struct中的字符串字段

go

我在寻找一个struct的字符串字段遍历,所以我可以做一些清理/验证(与strings.TrimSpacestrings.Trim等)。

现在,我有一个混乱的开关盒,它并没有真正的可扩展性,而且由于这并不是我的应用程序(Web表单)的热点,因此reflect在这里利用杠杆作用似乎是一个不错的选择。

我对于如何实现此功能有点障碍,反射文档对我来说有点困惑(我一直在研究其他一些验证包,但是它们太笨重了,我正在使用大猩猩/模式已用于解组部分):

  • 遍历该结构
  • 对于字符串类型的每个字段,请从strings包中应用我需要的任何内容,即field = strings.TrimSpace(field)
  • 如果存在field.Tag.Get(“ max”),我们将使用该值(strconv.Atoi,然后unicode.RuneCountInString)
  • 提供也与错误接口类型兼容的错误片
        type FormError []string         

    type Listing struct {
            Title string `max:"50"`
            Location string `max:"100"`
            Description string `max:"10000"`
            ExpiryDate time.Time
            RenderedDesc template.HTML
            Contact string `max:"255"`
        }

        // Iterate over our struct, fix whitespace/formatting where possible
        // and return errors encountered
        func (l *Listing) Validate() error {

           typ := l.Elem().Type()

           var invalid FormError
           for i = 0; i < typ.NumField(); i++ {
               // Iterate over fields
               // For StructFields of type string, field = strings.TrimSpace(field)
               // if field.Tag.Get("max") != "" {
               //     check max length/convert to int/utf8.RuneCountInString
                      if max length exceeded, invalid = append(invalid, "errormsg")
           }

           if len(invalid) > 0 {
               return invalid
           } 

           return nil
       }


       func (f FormError) Error() string {
           var fullError string
           for _, v := range f {
               fullError =+ v + "\n"
           }
           return "Errors were encountered during form processing: " + fullError
       }

提前致谢。


阅读 322

收藏
2020-07-02

共1个答案

一尘不染

您想要的主要是reflect.Value上称为NumFields() int和的方法Field(int)。您唯一真正缺少的是字符串检查和SetString方法。

    package main

    import "fmt"
    import "reflect"
    import "strings"

    type MyStruct struct {
        A,B,C string
        I int
        D string
        J int
    }

    func main() {
        ms := MyStruct{"Green ", " Eggs", " and ", 2, " Ham      ", 15}
        // Print it out now so we can see the difference
        fmt.Printf("%s%s%s%d%s%d\n", ms.A, ms.B, ms.C, ms.I, ms.D, ms.J)

        // We need a pointer so that we can set the value via reflection
        msValuePtr := reflect.ValueOf(&ms)
        msValue := msValuePtr.Elem()

        for i := 0; i < msValue.NumField(); i++ {
            field := msValue.Field(i)

            // Ignore fields that don't have the same type as a string
            if field.Type() != reflect.TypeOf("") {
                continue
            }

            str := field.Interface().(string)
            str = strings.TrimSpace(str)
            field.SetString(str)
        }
        fmt.Printf("%s%s%s%d%s%d\n", ms.A, ms.B, ms.C, ms.I, ms.D, ms.J)
    }

(游乐场链接)

这里有两个警告:

  1. 您需要一个指向要更改的指针。如果有值,则需要返回修改后的结果。

  2. 尝试修改未导出的字段通常会引起反射恐慌。如果您打算修改未导出的字段,请确保在包内执行此操作。

该代码相当灵活,如果您需要根据类型的不同行为,则可以使用switch语句或类型switch(在field.Interface()返回的值上)。

编辑:至于标记行为,您似乎已经知道了。一旦有了字段并检查了它是否是字符串,就可以field.Tag.Get("max")从那里使用并解析它。

Edit2:我在标签上犯了一个小错误。标签是反射类型的一部分。结构的类型,因此可以使用它们(有点麻烦)msValue.Type().Field(i).Tag.Get("max")

(您在注释中发布的带有有效Tag的代码的Playground版本)。

2020-07-02