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