起步
利用 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() 做类型转换。
还不快抢沙发