反射是一个复杂的话题。在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标签来进行字符串序列化和序列化,我们并未处理一些特殊情况,例如字符串包含 ‘:’ 或 ‘;’。针对于本示例,需要注意:
- 如果字段是字符串,则将对其进行序列化/反序列化。
- 如果字段不是字符串,则将忽略该字段。
- 如果字段的struct标记不包含”serialize”,则需要进行额外操作以保证序列化/反序列化正确完成。
- 没有考虑处理重复项。
- 如果未指定struct标记,则简单使用字段名称。
- 如果指定了标签为’-‘ ,则即使该字段是字符串,也会忽略。
- 还需要注意的是,反射不能与非导出值一起使用。
一个完善的反射操作需要考虑的细节很多,因此,在面对一个不是那么完美的第三方反射库时,尽量保持仁慈之心是值得赞美的。
最后编辑: kuteng 文档更新时间: 2021-01-03 15:03 作者:kuteng