简体   繁体   English

迭代 Struct 中的字符串字段

[英]Iterate Over String Fields in Struct

I'm looking to iterate over the string fields of a struct so I can do some clean-up/validation (with strings.TrimSpace , strings.Trim , etc).我希望遍历结构的字符串字段,以便我可以进行一些清理/验证(使用strings.TrimSpacestrings.Trim等)。

Right now I have a messy switch-case that's not really scalable, and as this isn't in a hot spot of my application (a web form) it seems leveraging reflect is a good choice here.现在我有一个杂乱的开关盒,它不是真正可扩展的,而且由于它不在我的应用程序(Web 表单)的热点中,因此在这里利用reflect似乎是一个不错的选择。

I'm at a bit of a roadblock for how to implement this however, and the reflect docs are a little confusing to me (I've been digging through some other validation packages, but they're way too heavyweight + I'm using gorilla/schema for the unmarshalling part already):然而,我在如何实现这一点上遇到了一些障碍,反射文档让我有点困惑(我一直在研究其他一些验证包,但它们太重量级了 + 我正在使用解组部分的大猩猩/模式已经):

  • Iterate over the struct遍历结构
  • For each field of type string, apply whatever I need to from the strings package ie field = strings.TrimSpace(field)对于字符串类型的每个字段,从strings包中应用我需要的任何内容,即field = strings.TrimSpace(field)
  • If there exists a field.Tag.Get("max"), we'll use that value (strconv.Atoi, then unicode.RuneCountInString)如果存在 field.Tag.Get("max"),我们将使用该值(strconv.Atoi,然后是 unicode.RuneCountInString)
  • Provide an error slice that's also compatible with the error interface type提供一个也与错误接口类型兼容的错误切片

    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 }

Thanks in advance.提前致谢。

What you want is primarily the methods on reflect.Value called NumFields() int and Field(int) .您想要的主要是reflect.Value 上的方法,称为NumFields() intField(int) The only thing you're really missing is the string check and SetString method.您唯一真正缺少的是字符串检查和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) (游乐场链接)

There are two caveats here:这里有两个警告:

  1. You need a pointer to what you're going to change.您需要一个指向您将要更改的内容的指针。 If you have a value, you'll need to return the modified result.如果您有一个值,则需要返回修改后的结果。

  2. Attempts to modify unexported fields generally will cause reflect to panic.尝试修改未导出的字段通常会导致反射恐慌。 If you plan on modifying unexported fields, make sure to do this trick inside the package.如果您打算修改未导出的字段,请确保在包内执行此技巧。

This code is rather flexible, you can use switch statements or type switches (on the value returned by field.Interface()) if you need differing behavior depending on the type.这段代码相当灵活,如果你需要根据类型不同的行为,你可以使用 switch 语句或类型开关(在 field.Interface() 返回的值上)。

Edit: As for the tag behavior, you seem to already have that figured out.编辑:至于标签行为,您似乎已经弄清楚了。 Once you have field and have checked that it's a string, you can just use field.Tag.Get("max") and parse it from there.一旦你有字段并检查它是一个字符串,你就可以使用field.Tag.Get("max")并从那里解析它。

Edit2: I made a small error on the tag. Edit2:我在标签上犯了一个小错误。 Tags are part of the reflect.Type of a struct, so to get them you can use (this is a bit long-winded) msValue.Type().Field(i).Tag.Get("max")标签是结构体的 reflect.Type 的一部分,所以你可以使用它们来获取它们(这有点啰嗦) msValue.Type().Field(i).Tag.Get("max")

( Playground version of the code you posted in the comments with a working Tag get). (您在评论中发布的带有工作标签的代码的Playground 版本)。

I got beat to the punch, but since I went to the work, here's a solution:我被打败了,但自从我上班以来,这里有一个解决方案:

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
}

I see you asked about how to do the tags.我看到你问过如何做标签。 Reflection has two components: a type and a value.反射有两个组成部分:类型和值。 The tag is associated with the type, so you have to get it separately than the field: listingType := reflect.TypeOf(*l) .标签与类型相关联,因此您必须与字段分开获取它: listingType := reflect.TypeOf(*l) Then you can get the indexed field and the tag from that.然后你可以从中获取索引字段和标签。

I don't know if it's a good way, but I use it like this.我不知道这是不是一个好方法,但我是这样使用的。

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

You can send the address of a struct to this function.您可以将结构的地址发送到此函数。 Sorry for My English is not very good.对不起,我的英语不是很好。

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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2025 STACKOOM.COM