Go中的结构体标签和反射

最后更新于:2022-04-02 06:47:33

## Go中的结构体标签和反射 反射是一个复杂的话题。在Go中反射最常用在处理结构体标签,其核心是处理键值字符串。即查找键,然后处理对应值。可以想象,在使用JSON marshal和unmarshal进行操作的时候,处理这些值存在很多复杂性。 反射包用于解析接口对象。它能够帮助我们查看结构类型,值,结构标签等。如果你需要处理的不仅仅是基本类型转换,那么这你应该关注reflect包的使用。 ### 实践 1. 创建serialize.go: ``` func SerializeStructStrings(s interface{}) (string, error) { result := "" // reflect.TypeOf使用传入的接口生成type类型 r := reflect.TypeOf(s) // reflect.ValueOf返回结构体每个字段对应的值 value := reflect.ValueOf(s) // 如果传入的是个结构体的指针 那么可以针对性的对其进行单独处理 if r.Kind() == reflect.Ptr { r = r.Elem() value = value.Elem() } // 循环所有的内部字段 for i := 0; i < r.NumField(); i++ { field := r.Field(i) // 字段的名称 key := field.Name // Lookup返回与标记字符串中的key关联的值。 如果密钥存在于标记中,则返回值(可以为空)。 // 否则返回的值将是空字符串。ok返回值报告是否在标记字符串中显式设置了值。 // 如果标记没有传统格式,则Lookup返回的值不做指定。 if serialize, ok := field.Tag.Lookup("serialize"); ok { // 忽略“ - ”否则整个值成为序列化'键' if serialize == "-" { continue } key = serialize } // 判断每个字段的类型 switch value.Field(i).Kind() { // 当前示例我们仅简单判断字符串 case reflect.String: result += key + ":" + value.Field(i).String() + ";" default: continue } } return result, nil } ``` 2. 建立deserialize.go : ``` package tags import ( "errors" "reflect" "strings" ) // DeSerializeStructStrings 反序列化字符串为对应的结构体 func DeSerializeStructStrings(s string, res interface{}) error { r := reflect.TypeOf(res) // 我们要求传入的必须是指针 if r.Kind() != reflect.Ptr { return errors.New("res must be a pointer") } // 解指针 // Elem返回r(Type类型)元素的type // 如果该type.Kind不是Array, Chan, Map, Ptr, 或 Slice会产生panic r = r.Elem() value := reflect.ValueOf(res).Elem() // 将传入的序列化字符串分割为map vals := strings.Split(s, ";") valMap := make(map[string]string) for _, v := range vals { keyval := strings.Split(v, ":") if len(keyval) != 2 { continue } valMap[keyval[0]] = keyval[1] } // 循环所有的内部字段 for i := 0; i < r.NumField(); i++ { field := r.Field(i) // 检查是否符合预置的tag if serialize, ok := field.Tag.Lookup("serialize"); ok { // 忽略'-'否则整个值成为序列化'键' if serialize == "-" { continue } // 判断是否处于map内 if val, ok := valMap[serialize]; ok { value.Field(i).SetString(val) } } else if val, ok := valMap[field.Name]; ok { // 是否是在map中的字段名称 value.Field(i).SetString(val) } } return nil } ``` 3. 建立 tags.go: ``` package tags import "fmt" // 注意Person内个字段的tag标签 type Person struct { Name string `serialize:"name"` City string `serialize:"city"` State string Misc string `serialize:"-"` Year int `serialize:"year"` } // EmptyStruct 演示了根据 tag 序列化和反序列化一个空结构体 func EmptyStruct() error { p := Person{} res, err := SerializeStructStrings(&p) if err != nil { return err } fmt.Printf("Empty struct: %#v\n", p) fmt.Println("Serialize Results:", res) newP := Person{} if err := DeSerializeStructStrings(res, &newP); err != nil { return err } fmt.Printf("Deserialize results: %#v\n", newP) return nil } // FullStruct 演示了根据 tag 序列化和反序列化一个非空结构体 func FullStruct() error { p := Person{ Name: "Aaron", City: "Seattle", State: "WA", Misc: "some fact", Year: 2017, } res, err := SerializeStructStrings(&p) if err != nil { return err } fmt.Printf("Full struct: %#v\n", p) fmt.Println("Serialize Results:", res) newP := Person{} if err := DeSerializeStructStrings(res, &newP); err != nil { return err } fmt.Printf("Deserialize results: %#v\n", newP) return nil } ``` 4. 建立main.go: ``` package main import ( "fmt" "github.com/agtorre/go-cookbook/chapter3/tags" ) func main() { if err := tags.EmptyStruct(); err != nil { panic(err) } fmt.Println() if err := tags.FullStruct(); err != nil { panic(err) } } ``` 5. 这会输出: ``` Empty struct: tags.Person{Name:"", City:"", State:"", Misc:"", Year:0} Serialize Results: name:;city:;State:; Deserialize results: tags.Person{Name:"", City:"", State:"", Misc:"", Year:0} Full struct: tags.Person{Name:"Aaron", City:"Seattle", State:"WA", Misc:"some fact", Year:2017} Serialize Results: name:Aaron;city:Seattle;State:WA; Deserialize results: tags.Person{Name:"Aaron", City:"Seattle", State:"WA", Misc:"", Year:0} ``` ### 说明 本节简单的展示了使用反射根据结构体的tag标签来进行字符串序列化和序列化,我们并未处理一些特殊情况,例如字符串包含 ':' 或 ';'。针对于本示例,需要注意: 1. 如果字段是字符串,则将对其进行序列化/反序列化。 2. 如果字段不是字符串,则将忽略该字段。 3. 如果字段的struct标记不包含"serialize",则需要进行额外操作以保证序列化/反序列化正确完成。 4. 没有考虑处理重复项。 5. 如果未指定struct标记,则简单使用字段名称。 6. 如果指定了标签为'-' ,则即使该字段是字符串,也会忽略。 还需要注意的是,反射不能与非导出值一起使用。 一个完善的反射操作需要考虑的细节很多,因此,在面对一个不是那么完美的第三方反射库时,尽量保持仁慈之心是值得赞美的。 * * * * 学识浅薄,错误在所难免。欢迎在群中就本书提出修改意见,以飨后来者,长风拜谢。 Golang中国(211938256) beego实战(258969317) Go实践(386056972)
';