Go中的结构体标签和反射
发布者:admin 发表于:444天前 阅读数:585 评论:0

反射是一个复杂的话题。在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标记,则简单使用字段名称。

如果指定了标签为’-‘ ,则即使该字段是字符串,也会忽略。

还需要注意的是,反射不能与非导出值一起使用。

一个完善的反射操作需要考虑的细节很多,因此,在面对一个不是那么完美的第三方反射库时,尽量保持仁慈之心是值得赞美的。