Iterate over string fields in a structure

I am looking for an iteration over the string fields of a structure, so I can do some cleaning / checking (with strings.TrimSpace , strings.Trim , etc.).

Right now I have a messy switch that is not very scalable, and since it is not in the hot spot of my application (web form), it seems that using reflect is a good choice here.

I am a little versed in how to implement this, but documents with documents confuse me a bit (I dug some other verification packages, but they are too heavy + I'm using gorillas / schemes for the non-marshalling part already):

  • Structure iterations
  • For each field of type string, apply everything I need from the package strings ie field = strings.TrimSpace(field)
  • If the .Tag.Get ("max") field exists, we will use this value (strconv.Atoi, then unicode.RuneCountInString)
  • Provide an error fragment that is 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.

+6
source share
2 answers

What you want is primarily reflection methods. The values ​​are called NumFields() int and Field(int) . The only thing you are missing is a string check and the 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) } 

(link on the playground)

There are two caveats here:

  • You need a pointer to what you are about to change. If you have a value, you need to return the modified result.

  • Attempts to change unexposed fields usually lead to panic. If you plan to modify the outstanding fields, be sure to do this trick inside the package.

This code is quite flexible, you can use switch statements or type switches (according to the value returned by the .Interface () field) if you need different behavior depending on the type.

Change Regarding tag behavior, you seem to already understand this. When you have a field and checked that it is a string, you can simply use field.Tag.Get("max") and field.Tag.Get("max") it there.

Edit2: I made a small mistake in the tag. Tags are part of reflection. The type of structure, so to get them you can use (this is a little long) msValue.Type().Field(i).Tag.Get("max")

( version of the playground code published in the comments using the working tag).

+10
source

I got kick to blow, but since I went to work, here is the 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 what you asked about how to make tags. Reflection has two components: type and value. The tag is associated with a type, so you should get it separately than the field: listingType := reflect.TypeOf(*l) . Then you can get the indexed field and tag from this.

+4
source

All Articles