技巧:Go 结构体如何转换成 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中valuefor 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{}, 8)queue := make([]interface{}, 0, 2)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 != "" {// 存入mapout[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 != "" {// 存入mapout[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了,但是要注意這種場景下結構體和嵌套結構體的字段就需要避免重復。
總結
以上是生活随笔為你收集整理的技巧:Go 结构体如何转换成 map[string]interface{}的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go 如何利用 Linux 内核的负载均
- 下一篇: 读者吐槽:Go 面试总被问到 RPC