反射是一个复杂的话题。在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