2014-01-21 14 views
6

我正在寻找迭代结构的字符串字段,所以我可以做一些清理/验证(使用strings.TrimSpace,strings.Trim等)。迭代结构中的字符串字段

现在我有一个混乱的开关柜,这不是真的可扩展的,因为这不是我的应用程序(网络表单)的热点,它似乎利用reflect是一个很好的选择。

但是,我对如何实现这一点有点阻碍,而且反射文档对我来说有点混乱(我一直在挖掘其他一些验证包,但它们太重量级了+我使用的是大猩猩/架构解组部分的话):

  • 遍历结构
  • 对于字符串类型的各个领域,适用任何我需要从strings包即field = strings.TrimSpace(field)
  • 如果有存在一个field.Tag.Get(“max”),我们将使用该值(strconv.Atoi,然后unicode.RuneCoun tInString)
  • 提供错误片这也与错误的接口类型

    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 
        } 
    

在此先感谢兼容。

回答

9

你想要的主要是reflect.Value的方法,称为NumFields() intField(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. 尝试修改未报告的字段通常会导致反应恐慌。如果您计划修改未导出的字段,请确保在包内执行此操作。

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

编辑:至于标签的行为,你似乎已经明白了。一旦你有了领域并且检查过它是一个字符串,你可以使用field.Tag.Get("max")并从那里解析它。

Edit2:我在标签上发生了一个小错误。标签是结构体的一部分,所以要让它们可以使用(这有点冗长)msValue.Type().Field(i).Tag.Get("max")

Playground version您发布的注释中带有一个工作标签的代码)。

+0

太棒了,你帮了很多忙。我的所有字段都被导出(结构也反映了我的数据库模式),但Validate与Listing相同,因此应该没问题。我仍然唯一的问题是使用'field.Tag.Get(“max”)' - 如果不是字段我应该调用'Tag'方法吗? http://play.golang.org/p/yMRLFCW4vt – elithrar

+1

我刚刚做了一个编辑。标签是结构本身的reflect.Type的一部分,所以你必须从msValue.Type()中重新获得该字段,然后从相应的字段中获取标签。 – LinearZoetrope

+0

太棒了 - 这个(http://play.golang.org/p/Uks300ZsS3)现在运行良好。我根据Tyson的回答声明了'listType:= reflect.TypeOf(* l)'来提供访问字段'Tag'的快捷方式。再次感谢你的帮助! – elithrar

4

我击败了一记重拳,但因为我去工作,这里有一个解决方案:

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)。然后你可以从中得到索引字段和标签。

+0

感谢代码+澄清类型与价值反映。我很好奇为什么我们要使用指向slice('* [] string')的指针作为自定义错误类型的基础?我不认为复制是这里的主要问题。我还假设'reflect.TypeOf(* l)'确保我们获得'l'的基础类型? – elithrar

+1

'[] * string'不是指向片的指针,它是一个字符串指针片。但是,不,我没有充分的理由使用指针 - 这只是我的习惯。是的,你取消引用'l'来获取结构的类型。如果你不这样做,你会得到指针的类型。 :-) – Tyson