## 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)
- 前言
- 第一章 I/O和文件系统
- 常见 I/O 接口
- 使用bytes和strings包
- 操作文件夹和文件
- 使用CSV格式化数据
- 操作临时文件
- 使用 text/template和HTML/templates包
- 第二章 命令行工具
- 解析命令行flag标识
- 解析命令行参数
- 读取和设置环境变量
- 操作TOML,YAML和JSON配置文件
- 操做Unix系统下的pipe管道
- 处理信号量
- ANSI命令行着色
- 第三章 数据类型转换和解析
- 数据类型和接口转换
- 使用math包和math/big包处理数字类型
- 货币转换和float64注意事项
- 使用指针和SQL Null类型进行编码和解码
- 对Go数据编码和解码
- Go中的结构体标签和反射
- 通过闭包实现集合操作
- 第四章 错误处理
- 错误接口
- 使用第三方errors包
- 使用log包记录错误
- 结构化日志记录
- 使用context包进行日志记录
- 使用包级全局变量
- 处理恐慌
- 第五章 数据存储
- 使用database/sql包操作MySQL
- 执行数据库事务接口
- SQL的连接池速率限制和超时
- 操作Redis
- 操作MongoDB
- 创建存储接口以实现数据可移植性
- 第六章 Web客户端和APIs
- 使用http.Client
- 调用REST API
- 并发操作客户端请求
- 使用OAuth2
- 实现OAuth2令牌存储接口
- 封装http请求客户端
- 理解GRPC的使用
- 第七章 网络服务
- 处理Web请求
- 使用闭包进行状态处理
- 请求参数验证
- 内容渲染
- 使用中间件
- 构建反向代理
- 将GRPC导出为JSON API
- 第八章 测试
- 使用标准库进行模拟
- 使用Mockgen包
- 使用表驱动测试
- 使用第三方测试工具
- 模糊测试
- 行为驱动测试
- 第九章 并发和并行
- 第十章 分布式系统
- 第十一章 响应式编程和数据流
- 第十二章 无服务器编程
- 第十三章 性能改进