[英]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.TrimSpace
、 strings.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):然而,我在如何实现这一点上遇到了一些障碍,反射文档让我有点困惑(我一直在研究其他一些验证包,但它们太重量级了 + 我正在使用解组部分的大猩猩/模式已经):
strings
package ie field = strings.TrimSpace(field)
strings
包中应用我需要的任何内容,即field = strings.TrimSpace(field)
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() int
和Field(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)
}
There are two caveats here:这里有两个警告:
You need a pointer to what you're going to change.您需要一个指向您将要更改的内容的指针。 If you have a value, you'll need to return the modified result.
如果您有一个值,则需要返回修改后的结果。
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.