Golang struct,map,json 之間的轉換

Golang struct,map,json 之間的轉換

起步

利用 Go 寫一個項目時,比如常見的 web server,很容易涉及到 struct,map,json 三者之間的轉換。這裏想簡單總結下,幫助一些剛入坑的朋友。

struct <=> json

不論是 struct => json 還是 json => struct 都尤爲簡單,這是因爲標準庫 encoding/json 提供了友好的 API。

示例代碼如下:

// struct_json_test.go
package main

import (
	"encoding/json"
	"log"
	"reflect"
	"testing"
)

// StructToJSON ...
func StructToJSON(o interface{}) string {
	// 確保 o 是結構體(沒有必要時,校驗代碼可以刪除)
	if reflect.TypeOf(o).Kind() != reflect.Struct {
		log.Fatal("need struct")
	}

	// []byte, error
	strJSON, _ := json.Marshal(o)
	// []byte => string
	return string(strJSON)
}

// JSONToStruct ...
func JSONToStruct(s string, targetPtr interface{}) {
	// string => []byte
	sBytes := []byte(s)
	// 填充目標結構體
	json.Unmarshal(sBytes, targetPtr)
}

// TestCurCode ...
func TestCurCode(t *testing.T) {
	// 定義一個 Student 結構體
	type Student struct {
		Name string
		Age  int
	}

	stu := Student{"zty", 18}
	strJSON := StructToJSON(stu)
	t.Logf("type: %T\n", strJSON)
	t.Logf("value: %s\n", strJSON)

	// 定義一個 JSON 字符串
	var JSONStr = `
	{
		"Name": "zty",
		"Age": 18
	}
	`
	target := Student{}
	JSONToStruct(JSONStr, &target)
	t.Logf("type: %T\n", target)
	t.Logf("value: %v\n", target)
}

struct 與 json 之間的轉換主要靠兩個 API:

  • json.Marshal
  • json.Unmarshal

需要注意 json.Unmarshal 的第二個參數是目標結構體的地址。

map <=> json

// struct_json_test.go
package main

import (
	"encoding/json"
	"testing"
)

// MapToJSON ...
func MapToJSON(m map[string]interface{}) string {
	strJSON, _ := json.Marshal(m)
	// []byte => string
	return string(strJSON)
}

// JSONToMap ...
func JSONToMap(s string) map[string]interface{} {
	// 初始化一個 map
	tarMap := make(map[string]interface{})
	// string => []byte
	sBytes := []byte(s)
	// 填充 map
	json.Unmarshal(sBytes, &tarMap)
	return tarMap
}

func TestCurCode(t *testing.T) {
	srcMap := map[string]interface{}{
		"Name": "zty",
		"Age":  18,
	}
	strJSON := MapToJSON(srcMap)
	t.Logf("type: %T", strJSON)
	t.Logf("value: %v", strJSON)

	var JSONStr = `
	{
		"Name": "zty",
		"Age":  18
	}
	`
	m := JSONToMap(JSONStr)
	t.Logf("type: %T", m)
	t.Logf("value: %v", m)
}

map 和 json 之間的轉換與 struct 和 json 之間的轉換同出一轍,調用的 API 也是一樣的,就不再多口舌了。

struct <=> map

在有了上面的知識儲備之後,我們可以很快想到 struct、json 轉換的第一種方法

  • struct => json => map
  • map => json => struct

即:以 json 作爲媒介,爲 struct 和 map 牽線。

第二種方法,利用官方提供的 reflect 庫。

// struct_map_test.go
package main

import (
	"log"
	"reflect"
	"testing"
)

// StructToMap ...
func StructToMap(o interface{}) map[string]interface{} {
	t := reflect.TypeOf(o)
	v := reflect.ValueOf(o)
	// 結構體類型校驗
	if t.Kind() != reflect.Struct {
		log.Fatal("need struct")
	}

	tarMap := make(map[string]interface{})
	for i := 0; i < t.NumField(); i++ {
		// t.Field(i).Name	返回 field 的名字
		// v.Field(i).Interface() 返回 field 對應值的 interface{} 類型
		tarMap[t.Field(i).Name] = v.Field(i).Interface()
	}
	return tarMap
}

func MapToStruct(m map[string]interface{}, target interface{}) {
	// 確保傳遞進來的是指針類型
	if reflect.TypeOf(target).Kind() == reflect.Ptr {
		// 確保指針指向的是結構體
		if reflect.TypeOf(target).Elem().Kind() == reflect.Struct {
			var (
				field reflect.StructField
				ok    bool
			)
			for k, v := range m {
				// 根據 map 鍵名,找到結構體中對應的字段域
				field, ok = reflect.TypeOf(target).Elem().FieldByName(k)
				// 確保字段類型與 map 中的元素類型一致
				if ok && field.Type == reflect.TypeOf(v) {
					// 找到對應字段,賦值
					reflect.ValueOf(target).Elem().FieldByName(k).Set(
						reflect.ValueOf(v),
					)
				}
			}
		}
	}
}

func TestCurCode(t *testing.T) {
	// 定義一個 Student 結構體
	type Student struct {
		Name string
		Age  int
	}
	m := StructToMap(Student{"zty", 18})
	t.Logf("type: %T", m)
	t.Logf("value: %v", m)

	srcMap := map[string]interface{}{
		"Name": "zty",
		"Age":  18,
	}
	target := Student{}
	MapToStruct(srcMap, &target)
	t.Logf("type: %T", target)
	t.Logf("value: %v", target)
}

這裏需要簡單說明一下上述代碼中用到的 reflect 庫的 API。

  • reflect.TypeOf()reflect.ValueOf()

前者用來獲取變量的static type,後者用來獲取變量對應的值。當然,得到的內容已經被封裝在結構體中了。

var zty string = "Hello World"
t := reflect.TypeOf(zty)
fmt.Printf("TypeOf: type(%T), value(%v)\n", t, t)

v := reflect.ValueOf(zty)
fmt.Printf("ValueOf: type(%T), value(%v)\n", v, v)

// 輸出:
TypeOf: type(*reflect.rtype), value(string)
TypeOf: type(reflect.Value), value(Hello World)

由於這些結構體實現了 String()方法,通過打印結構體對象,可以獲取到它們存儲的核心數據。前者是 string,後者是Hello World。印證了前面的話,reflect.TypeOf() 存儲變量類型,reflect.ValueOf() 存儲變量內容。

  • reflect.TypeOf(v).Kind()

reflect.TypeOf() 方法只是返回了存放類型的結構體,而調用這個結構體的 Kind() 方法能夠獲取到這個類型的類別。類別表示“哪一類”的意思,如 *int, *string 都屬於指針類型;type x struct {...}type y struct {...} x、y 都屬於結構體類型。在 reflect 庫中提供了 reflect.Struct、reflect.Ptr 等枚舉來與 Kind() 的返回值判等。

  • reflect.TypeOf(v).Field()reflect.TypeOf(v).FieldName()reflect.ValueOf(v).Field()reflect.ValueOf(v).FieldName()

以上接口 reflect.TypeOf* 都是用來獲取結構體中的字段名、字段類型等屬性;reflect.ValueOf* 用來獲取字段對應的值。Field(i int) 通過索引值拿到字段/值,FieldName(name string) 通過字段名拿到字段/值。

type Student struct {
	Name string
}

stu := Student{"zty"}
// TypeOf
fieldByFieldTypeof := reflect.TypeOf(stu).Field(0)
fmt.Printf("TypeOf Field: type(%T), value(%v)\n", fieldByFieldTypeof, fieldByFieldTypeof)
fieldByFieldNameTypeof, _ := reflect.TypeOf(stu).FieldByName("Name")
fmt.Printf("TypeOf FieldName: type(%T), value(%v)\n", fieldByFieldNameTypeof, fieldByFieldNameTypeof)
// ValueOf
fieldByFieldValueof := reflect.ValueOf(stu).Field(0)
fmt.Printf("ValueOf Field: type(%T), value(%v)\n", fieldByFieldValueof, fieldByFieldValueof)
fieldByFieldNameValueof := reflect.ValueOf(stu).FieldByName("Name")
fmt.Printf("ValueOf FieldName: type(%T), value(%v)\n", fieldByFieldNameValueof, fieldByFieldNameValueof)

// 輸出:
TypeOf Field: type(reflect.StructField), value({Name  string  0 [0] false})
TypeOf FieldName: type(reflect.StructField), value({Name  string  0 [0] false})

ValueOf Field: type(reflect.Value), value(zty)
ValueOf FieldName: type(reflect.Value), value(zty)

注意:

reflect.TypeOf(stu).FieldByName(“Name”) 與 reflect.ValueOf(stu).FieldByName(“Name”) 的返回值個數不同。前者還會返回一個 bool 值,用來說明該字段是否存在;後者只有一個返回值,當這個字段不存在時,返回 refelct.Invalid 類型,打印出來的效果是:

  • reflect.TypeOf(v).Elem()reflect.ValueOf(v).Elem()

當傳入的 v 是一根指針時,就可以調用 Elem() 方法,表示獲取指針指向的結構體,然後仍然是層層封裝,最後得到的類型分別是 *reflect.rtype、reflect.Value。也就是說,當 v 是指針的時候,reflect.TypeOf(v).Elem() 等同於 reflect.TypeOf(*v)reflect.ValueOf(v).Elem() 等同於 reflect.ValueOf(*v)

  • reflect.ValueOf(v).Elem().FieldByName("k").Set()

爲結構體中的字段賦值。Set() 接收的是 reflect.Value 類型,因此傳入的值需要先經過 reflect.VaueOf() 做類型轉換。

感謝

  • 參考 https://www.cnblogs.com/liang1101/p/6741262.html
  • 參考 https://time.geekbang.org/course/detail/160-87797
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章