Golang struct,map,json 之间的转换


文章目录

  • Golang struct,map,json 之间的转换
  • 起步
  • struct <=> json
  • map <=> json
  • struct <=> map
  • 感谢


起步

利用 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://time.geekbang.org/course/detail/160-87797