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)
';