Iterate Over String Fields in Struct

后端 未结 3 547
夕颜
夕颜 2020-12-28 11:58

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).

<
相关标签:
3条回答
  • 2020-12-28 12:10

    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). Then you can get the indexed field and the tag from that.

    0 讨论(0)
  • 2020-12-28 12:13

    I don't know if it's a good way, but I use it like this.

    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
    }
    
    0 讨论(0)
  • 2020-12-28 12:22

    What you want is primarily the methods on reflect.Value called NumFields() int and Field(int). The only thing you're really missing is the string check and SetString method.

    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.

    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.

    Edit2: I made a small error on the tag. 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")

    (Playground version of the code you posted in the comments with a working Tag get).

    0 讨论(0)
提交回复
热议问题