迭代结构中的字符串字段

eli*_*rar 7 go

我期待遍历一个struct的字符串字段,所以我可以做一些清理/验证(与strings.TrimSpace,strings.Trim等).

现在我有一个凌乱的交换机案例,它不是真正可扩展的,因为这不是我的应用程序(网络表单)的热点,它似乎利用reflect是一个很好的选择.

然而,我对如何实现这个问题有一些障碍,而且反射文档对我来说有点混乱(我一直在挖掘其他一些验证包,但它们太重量级了+我正在使用gorilla/schema for the unmarshalling part已经):

  • 迭代结构
  • 对于字符串类型的每个字段,从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
       }
    
    Run Code Online (Sandbox Code Playgroud)

提前致谢.

Lin*_*ope 12

你想要的主要是关于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)
}
Run Code Online (Sandbox Code Playgroud)

(游乐场链接)

这里有两个警告:

  1. 你需要一个指向你要改变的东西的指针.如果您有值,则需要返回修改后的结果.

  2. 尝试修改未导出的字段通常会导致反映出现恐慌.如果您计划修改未导出的字段,请确保在包内执行此操作.

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

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

Edit2:我在标签上犯了一个小错误.标签是结构的reflect.Type的一部分,所以为了得到它们你可以使用(这有点啰嗦)msValue.Type().Field(i).Tag.Get("max")

(您在评论中发布的代码的Playground版本,带有工作标记get).


Tys*_*son 5

我受到了打击,但自从我开始工作以来,这是一个解决方案:

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
}
Run Code Online (Sandbox Code Playgroud)

我看到你问过如何做标签.Reflection有两个组件:类型和值.标签与类型相关联,因此您必须单独获取它而不是字段:listingType := reflect.TypeOf(*l).然后,您可以从中获取索引字段和标记.