## 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包
- 使用表驅動測試
- 使用第三方測試工具
- 模糊測試
- 行為驅動測試
- 第九章 并發和并行
- 第十章 分布式系統
- 第十一章 響應式編程和數據流
- 第十二章 無服務器編程
- 第十三章 性能改進