title: '【轉載】結構體轉 map [string] interface {} 的若干方法'
date: 2021-08-09 16:34:33
comment: false
toc: true
category:
- Golang
tags: - 轉載
- Go
- Map
- Struct
- String
- Interface
- 方法
本文轉載自:結構體轉 map [string] interface {} 的若干方法 | 李文周的博客
本文介紹了 Go 語言中將結構體轉成map[string]interface{}
時你需要了解的 “坑”,也有你需要知道的若干方法。
我們在 Go 語言中通常使用結構體來保存我們的數據,例如要存儲用戶信息,我們可能會定義如下結構體:
// UserInfo 用戶信息
type UserInfo struct {
Name string `json:"name"`
Age int `json:"age"`
}
u1 := UserInfo{Name: "q1mi", Age: 18}
假設現在要將上面的u1
轉換成map[string]interface{}
,該如何操作呢?
結構體轉 map [string] interface {}#
JSON 序列化方式#
這不是很簡單嗎?我用 JSON 序列化一下 u1,再反序列化成 map 不就可以了麼。說幹就幹,代碼如下:
func main() {
u1 := UserInfo{Name: "q1mi", Age: 18}
b, _ := json.Marshal(&u1)
var m map[string]interface{}
_ = json.Unmarshal(b, &m)
for k, v := range m{
fmt.Printf("key:%v value:%v\n", k, v)
}
}
輸出:
key:name value:q1mi
key:age value:18
看起來沒什麼問題,但其實這裡是有一個 “坑” 的。那就是 Go 語言中的json
包在序列化空接口存放的數字類型(整型、浮點型等)都會序列化成 float64 類型。
也就是上面例子中m["age"]
現在底層是一個float64
了,不是個int
了。我們來驗證下:
func main() {
u1 := UserInfo{Name: "q1mi", Age: 18}
b, _ := json.Marshal(&u1)
var m map[string]interface{}
_ = json.Unmarshal(b, &m)
for k, v := range m{
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
}
輸出:
key:name value:q1mi value type:string
key:age value:18 value type:float64
很顯然,出現了一個意料之外的行為,我們需要想辦法規避掉。
反射#
沒辦法就需要自己動手去實現了。這裡使用反射遍歷結構體字段的方式生成 map,具體代碼如下:
// ToMap 結構體轉為Map[string]interface{}
func ToMap(in interface{}, tagName string) (map[string]interface{}, error){
out := make(map[string]interface{})
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct { // 非結構體返回錯誤提示
return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
}
t := v.Type()
// 遍歷結構體字段
// 指定tagName值為map中key;字段值為map中value
for i := 0; i < v.NumField(); i++ {
fi := t.Field(i)
if tagValue := fi.Tag.Get(tagName); tagValue != "" {
out[tagValue] = v.Field(i).Interface()
}
}
return out, nil
}
驗證一下:
m2, _ := ToMap(&u1, "json")
for k, v := range m2{
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
輸出:
key:name value:q1mi value type:string
key:age value:18 value type:int
這一次map["age"]
的類型就對了的。
第三方庫 structs#
除了自己實現,Github 上也有現成的輪子,例如第三方庫:https://github.com/fatih/structs。
它使用的自定義結構體 tag 是structs
:
// UserInfo 用戶信息
type UserInfo struct {
Name string `json:"name" structs:"name"`
Age int `json:"age" structs:"age"`
}
用法很簡單:
m3 := structs.Map(&u1)
for k, v := range m3 {
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
structs
這個包也有很多其他的使用示例,大家可以去查看文檔。但是需要注意的是目前這個庫已經被作者設置為只讀了。
嵌套結構體轉 map [string] interface {}#
structs
本身是支持嵌套結構體轉map[string]interface{}
的,遇到結構體嵌套它會轉換為map[string]interface{}
嵌套map[string]interface{}
的模式。
我們定義一組嵌套的結構體如下:
// UserInfo 用戶信息
type UserInfo struct {
Name string `json:"name" structs:"name"`
Age int `json:"age" structs:"age"`
Profile `json:"profile" structs:"profile"`
}
// Profile 配置信息
type Profile struct {
Hobby string `json:"hobby" structs:"hobby"`
}
聲明結構體變量 u1:
u1 := UserInfo{Name: "q1mi", Age: 18, Profile: Profile{"双色球"}}
第三方庫 structs#
代碼和上面的其實是相同的:
m3 := structs.Map(&u1)
for k, v := range m3 {
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
輸出結果:
key:name value:q1mi value type:string
key:age value:18 value type:int
key:profile value:map[hobby:双色球] value type:map[string]interface {}
從結果來看最後嵌套字段profile
是map[string]interface {}
,屬於 map 嵌套 map。
使用反射轉成單層 map#
如果我們想把嵌套的結構體轉換成一個單層 map 該怎麼做呢?
我們把上面反射的代碼稍微修改一下就可以了:
// ToMap2 將結構體轉為單層map
func ToMap2(in interface{}, tag string) (map[string]interface{}, error) {
// 當前函數只接收struct類型
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr { // 結構體指針
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
}
out := make(map[string]interface{})
queue := make([]interface{}, 0, 1)
queue = append(queue, in)
for len(queue) > 0 {
v := reflect.ValueOf(queue[0])
if v.Kind() == reflect.Ptr { // 結構體指針
v = v.Elem()
}
queue = queue[1:]
t := v.Type()
for i := 0; i < v.NumField(); i++ {
vi := v.Field(i)
if vi.Kind() == reflect.Ptr { // 內嵌指針
vi = vi.Elem()
if vi.Kind() == reflect.Struct { // 結構體
queue = append(queue, vi.Interface())
} else {
ti := t.Field(i)
if tagValue := ti.Tag.Get(tag); tagValue != "" {
// 存入map
out[tagValue] = vi.Interface()
}
}
break
}
if vi.Kind() == reflect.Struct { // 內嵌結構體
queue = append(queue, vi.Interface())
break
}
// 一般字段
ti := t.Field(i)
if tagValue := ti.Tag.Get(tag); tagValue != "" {
// 存入map
out[tagValue] = vi.Interface()
}
}
}
return out, nil
}
測試一下:
m4, _ := ToMap2(&u1, "json")
for k, v := range m4 {
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
輸出:
key:name value:q1mi value type:string
key:age value:18 value type:int
key:hobby value:双色球 value type:string
這下我們就把嵌套的結構體轉為單層的 map 了,但是要注意這種場景下結構體和嵌套結構體的字段就需要避免重複。