banner
biuaxia

biuaxia

"万物皆有裂痕,那是光进来的地方。"
github
bilibili
tg_channel

【轉載】結構體轉map_string_interface的若干方法

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 {}

從結果來看最後嵌套字段profilemap[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 了,但是要注意這種場景下結構體和嵌套結構體的字段就需要避免重複。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。