这里我们讲一下Go
语言中,结构体对象、map[string]interface{}
、JSON
字符串这三者的相互转换。
首先是结构体对象和map
转换成JSON
字符串,使用json.Marshal
这个函数:
func Marshal(v any) ([]byte, error)
这里的参数v
,放置结构体对象本身,或者它的引用都可以。
得到的是[]byte
类型的JSON
,我们使用string()
转换一下类型就得到JSON
字符串了,代码如下所示:
type Person struct {
Name string `json:"name,omitempty"`
Age int `json:"age,omitempty"`
}
func main() {
person := Person{
Name: "Alice",
Age: 30,
}
dataMap := map[string]interface{}{
"name": "Bob",
"age": 25,
}
personJSONByte, _ := json.Marshal(person)
personJSONStr := string(personJSONByte)
mapJSONByte, _ := json.Marshal(dataMap)
mapJsonStr := string(mapJSONByte)
}
反过来,JSON
字符串转换成结构体对象和map
,就要使用到json.Unmarshal
函数:
func Unmarshal(data []byte, v any) error
第一个参数是[]byte
类型的JSON
,第二个参数必须是结构体对象或者map
的指针,代码如下所示:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonStr := `{"name":"Alice","age":30}`
var person Person
_ = json.Unmarshal([]byte(jsonStr), &person)
var dataMap map[string]interface{}
_ = json.Unmarshal([]byte(jsonStr), &dataMap)
}
结构体对象与JSON
互转、map
与JSON
互转都是很方便的,但是结构体对象与map
互转就有些麻烦了。
首先我们看结构体对象转map[string]interface{}
,有下面这样一个函数:
func structToMap(obj interface{}) map[string]interface{} {
objMap := make(map[string]interface{})
val := reflect.ValueOf(obj)
typ := reflect.TypeOf(obj)
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
if field.PkgPath != "" && !field.Anonymous {
continue
}
objMap[field.Name] = val.Field(i).Interface()
}
return objMap
}
利用了 Go
的反射机制,动态地获取了结构体对象的字段和值,并转换为map[string]interface{}
。不过,这并不是最佳解决方案,因为这需要我们手动去写反射的代码,并且在每个项目模块中都需要对其进行引入。
有一个第三方包很好地帮我们处理了这个问题,它就是github.com/fatih/structs
:
go get -u github.com/fatih/structs
它的核心方法就是Map
函数,函数签名如下:
func Map(s interface{}) map[string]interface{}
它的入参是结构体对象,出参是map[string]interface{}
。这里入参可以是对象本身,也可以是对象的引用。
这里需要注意的一点是,默认情况下,转换后的map[string]interface{}
的key
值与结构体字段名完全一致,即首字母大写。如果想让map
的key
值和结构体字段名构成映射,需要structs
标签,这里在标签里设置omitempty
可以让结构体的空值字段不写入到map
。
还需要注意,结构体的私有字段会被忽略。
使用示例如下所示:
type Person struct {
Name string `structs:"name,omitempty"`
Age int `structs:"age,omitempty"`
// 私有字段不会被转换到 map
city string
Country string `structs:"country,omitempty"`
}
func main() {
person := &Person{
Name: "Alice",
Age: 30,
city: "New York",
Country: "USA",
}
personMap := structs.Map(person)
fmt.Println(personMap) // map[age:30 country:USA name:Alice]
}
还有两个函数,一个用于获取所有字段名,一个用于获取所有字段值,函数签名如下:
func Names(s interface{}) []string
func Values(s interface{}) []interface{}
接下来就是map[string]interface{}
转结构体对象,就更加麻烦了。map
的值是任意类型,因此需要确保转换时类型匹配,否则会导致运行时错误,我们要对每个字段进行类型断言。
type Person struct {
Name string
Age int
}
func main() {
dataMap := map[string]interface{}{
"name": "Alice",
"age": 30,
}
person := mapToStruct(dataMap)
}
func mapToStruct(dataMap map[string]interface{}) Person {
person := Person{}
if name, ok := dataMap["name"].(string); ok {
person.Name = name
}
if age, ok := dataMap["age"].(int); ok {
person.Age = age
}
return person
}
非常难写且不通用,如果字段一多,代码量会是难以控制的,如果有嵌套字段,又会增加代码的复杂度。
有一个第三方包可以解决这个问题,就是github.com/mitchellh/mapstructure
:
go get -u github.com/mitchellh/mapstructure
这里我们使用Decode
函数解决这个问题,它的函数签名如下:
func Decode(input interface{}, output interface{}) error
其中参数input
是我们指定的map[string]interface{}
,output
是要转换的结构体对象。使用示例如下:
type Person struct {
Name string `mapstructure:"name"`
Age int `mapstructure:"age"`
City string `mapstructure:"city"`
}
func main() {
data := map[string]interface{}{
"name": "Alice",
"age": 30,
"city": "New York",
}
person := &Person{}
_ = mapstructure.Decode(data, person)
}
这里需要注意的是output
参数必须为已初始化的结构体对象的指针,否则会报错。还有一点就是这里的结构体标签是mapstructure
,用来和map[string]interface{}
的key
进行映射。
Decode
函数也可以处理嵌套结构体,示例如下:
type Address struct {
City string `mapstructure:"city"`
Country string `mapstructure:"country"`
}
type Person struct {
Name string `mapstructure:"name"`
Age int `mapstructure:"age"`
Address Address `mapstructure:"address"`
}
func main() {
data := map[string]interface{}{
"name": "Alice",
"age": 30,
"address": map[string]interface{}{
"city": "New York",
"country": "USA",
},
}
person := &Person{}
_ = mapstructure.Decode(data, person)
}
到这里,三种数据的互相转换就讲解完毕了。