【问题标题】:Iterate Over String Fields in Struct迭代结构中的字符串字段
【发布时间】:2014-01-21 00:02:48
【问题描述】:

我希望遍历结构的字符串字段,以便进行一些清理/验证(使用strings.TrimSpace、strings.Trim 等)。

现在我有一个杂乱无章的开关盒,它不是真正可扩展的,而且由于它不在我的应用程序(网络表单)的热点中,因此利用 reflect 似乎是一个不错的选择。

然而,我在如何实现这一点上遇到了一些障碍,并且反射文档让我有点困惑(我一直在研究其他一些验证包,但它们太重了 + 我'我已经在使用 gorilla/schema 作为解组部分):

  • 遍历结构
  • 对于字符串类型的每个字段,从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
       }
    

提前致谢。

【问题讨论】:

    标签: go


    【解决方案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)
    }
    

    (Playground link)

    这里有两个警告:

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

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

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

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

    Edit2:我在标签上犯了一个小错误。标签是结构的 reflect.Type 的一部分,因此您可以使用它们(这有点冗长)msValue.Type().Field(i).Tag.Get("max")

    (Playground version 您在 cmets 中发布的带有工作标记的代码获取)。

    【讨论】:

    • 太棒了,你帮了很多忙。我的所有字段都已导出(该结构也反映了我的 DB 模式),但 Validate 与 Listing 位于同一个包中,所以应该没问题。我仍然存在的唯一问题是使用field.Tag.Get("max") - 如果没有字段,我应该调用Tag 方法吗? play.golang.org/p/yMRLFCW4vt
    • 我刚刚进行了编辑。标签是结构体本身的reflect.Type的一部分,所以需要从msValue.Type()重新获取字段,然后从对应字段中获取Tag。
    • 太棒了 - 这个 (play.golang.org/p/Uks300ZsS3) 现在运行良好。我根据泰森的回答声明了listType := reflect.TypeOf(*l),以提供访问Tag 字段的快捷方式。再次感谢您的帮助!
    【解决方案2】:

    不知道好不好用,我就是这样用的。

    https://play.golang.org/p/aQ_hG2BYmMD

    您可以将结构的地址发送到此函数。 对不起,我的英语不是很好。

    trimStruct(&someStruct)
    
    func trimStruct(v interface{}) {
        bytes, err := json.Marshal(v)
        if err != nil {
            fmt.Println("[trimStruct] Marshal Error :", err)
        }
        var mapSI map[string]interface{}
        if err := json.Unmarshal(bytes, &mapSI); err != nil {
            fmt.Println("[trimStruct] Unmarshal to byte Error :", err)
        }
        mapSI = trimMapStringInterface(mapSI).(map[string]interface{})
        bytes2, err := json.Marshal(mapSI)
        if err != nil {
            fmt.Println("[trimStruct] Marshal Error :", err)
        }
        if err := json.Unmarshal(bytes2, v); err != nil {
            fmt.Println("[trimStruct] Unmarshal to b Error :", err)
        }
    }
    
    func trimMapStringInterface(data interface{}) interface{} {
        if values, valid := data.([]interface{}); valid {
            for i := range values {
                data.([]interface{})[i] = trimMapStringInterface(values[i])
            }
        } else if values, valid := data.(map[string]interface{}); valid {
            for k, v := range values {
                data.(map[string]interface{})[k] = trimMapStringInterface(v)
            }
        } else if value, valid := data.(string); valid {
            data = strings.TrimSpace(value)
        }
        return data
    }
    

    【讨论】:

      【解决方案3】:

      我被打败了,但既然我去上班了,这里有一个解决办法:

      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 {
          listingType := reflect.TypeOf(*l)
          listingValue := reflect.ValueOf(l)
          listingElem := listingValue.Elem()
      
          var invalid FormError = []*string{}
          // Iterate over fields
          for i := 0; i < listingElem.NumField(); i++ {
              fieldValue := listingElem.Field(i)
              // For StructFields of type string, field = strings.TrimSpace(field)
              if fieldValue.Type().Name() == "string" {
                  newFieldValue := strings.TrimSpace(fieldValue.Interface().(string))
                  fieldValue.SetString(newFieldValue)
      
                  fieldType := listingType.Field(i)
                  maxLengthStr := fieldType.Tag.Get("max")
                  if maxLengthStr != "" {
                      maxLength, err := strconv.Atoi(maxLengthStr)
                      if err != nil {
                          panic("Field 'max' must be an integer")
                      }
                      //     check max length/convert to int/utf8.RuneCountInString
                      if utf8.RuneCountInString(newFieldValue) > maxLength {
                          //     if max length exceeded, invalid = append(invalid, "errormsg")
                          invalidMessage := `"`+fieldType.Name+`" is too long (max allowed: `+maxLengthStr+`)`
                          invalid = append(invalid, &invalidMessage)
                      }
                  }
              }
          }
      
          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
      }
      

      我看到你问过如何做标签。反射有两个组成部分:类型和值。标签与类型相关联,因此您必须与字段分开获取它:listingType := reflect.TypeOf(*l)。然后你可以从中获取索引字段和标签。

      【讨论】:

      • 感谢代码 + 澄清类型与反射值。我很好奇为什么我们会使用指向切片 (*[]string) 的指针作为自定义错误类型的基础?我不认为复制是这里的主要问题。我还假设reflect.TypeOf(*l) 确保我们得到l 的底层类型?
      • []*string 不是指向切片的指针,而是字符串指针的切片。但是不,我真的没有充分的理由使用指针——这只是我这样做的习惯。是的,您取消引用l 以获取结构的类型。如果你不这样做,你会得到指针的类型。 :-)
      猜你喜欢
      • 1970-01-01
      • 2022-12-19
      • 2014-04-21
      • 2012-10-23
      • 2011-06-03
      • 1970-01-01
      • 2022-01-22
      • 2013-01-13
      • 2021-10-31
      相关资源
      最近更新 更多