我正在寻找迭代结构的字符串字段,以便我可以进行一些清理/验证(使用strings.TrimSpace
,strings.Trim
等)。
现在我有一个凌乱的交换机案例,它不是真正可扩展的,因为这不是我的应用程序(网络表单)的热点,似乎利用reflect
是一个很好的选择。< / p>
但是我对如何实现这一点有一个障碍,反思文档对我来说有点混乱(我一直在挖掘其他一些验证包,但它们太重量了我+我已经使用gorilla / schema作为解组部分了:
strings
包中应用我需要的任何内容,即field = strings.TrimSpace(field)
提供与错误接口类型
兼容的错误切片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
}
提前致谢。
答案 0 :(得分:12)
你想要的主要是关于reflect.Value的方法,名为NumFields() int
和Field(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)
}
这里有两点需要注意:
你需要一个指向你要改变的东西的指针。如果您有值,则需要返回修改后的结果。
尝试修改未导出的字段通常会导致反射出现恐慌。如果您打算修改未导出的字段,请确保在包中执行此操作。
此代码相当灵活,如果根据类型需要不同的行为,可以使用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
}