Go语言结构转换

这里我们讲一下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互转、mapJSON互转都是很方便的,但是结构体对象与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值与结构体字段名完全一致,即首字母大写。如果想让mapkey值和结构体字段名构成映射,需要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)
}

到这里,三种数据的互相转换就讲解完毕了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值