迭代结构中的字符串字段

时间:2014-01-21 00:02:48

标签: go

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

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

但是我对如何实现这一点有一个障碍,反思文档对我来说有点混乱(我一直在挖掘其他一些验证包,但它们太重量了我+我已经使用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
       }
    

提前致谢。

3 个答案:

答案 0 :(得分: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)
}

(Playground link)

这里有两点需要注意:

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

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

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

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

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

    (您在评论中发布的代码的Playground version,带有正常的标记)。

答案 1 :(得分: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
}

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

答案 2 :(得分:1)

我不知道这是不是一种好方法,但是我是这样使用的。

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
}