写在前面
最近项目中多次遇到需要将结构体转换成map的操作,于是在网上搜了一下相关实现
方法一:序列化和反序列化
这种方法比较简单,也就是先将结构体序列化成字节,再把字节序列化成map
func StructToMapV1(src interface{}) (map[string]interface{}, error) {
data, err := json.Marshal(src)
if err != nil {
return nil, errors.Wrapf(err, "[StructToMapV1] Marshal src:%+v", src)
}
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
return nil, errors.Wrapf(err, "[StructToMapV1] Unmarshal src:%+v", src)
}
return result, nil
}
优点
1.比较容易理解和实现
缺点
1.如果tag中有omitempty则会忽略空字段,有的时候不想忽略空字段,就不能用此方法了
方法二:反射
func StructToMapV2(in interface{}) (map[string]interface{}, error) {
out := make(map[string]interface{})
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct { // 非结构体返回错误提示
return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
}
t := v.Type()
// 遍历结构体字段
tagName := "json"
// 指定tagName值为map中key;字段值为map中value
for i := 0; i < v.NumField(); i++ {
fi := t.Field(i)
if tagValue := fi.Tag.Get(tagName); tagValue != "" {
tagValues := strings.Split(tagValue, ",")
fieldName := strings.Trim(tagValues[0], " ")
out[fieldName] = v.Field(i).Interface()
}
}
return out, nil
}
优点
1.不会忽略空字段
缺点
1.实现难度大,需要对反射有一定的了解
2.反射影响性能
方法三:使用第三方包github.com/fatih/structs
func StructToMapV3(in interface{}) map[string]interface{} {
return structs.Map(in)
}
缺点
1.需要在tag中指定structs
标签,如果标签中有omitempty也会忽略
其实它底层也是使用了反射
性能分析
代码
type testStruct struct {
Name string `json:" name ,omitempty" structs:"name"`
Age int `json:"age,omitempty" structs:"age,omitempty"`
IsMarry bool `json:"is_marry,omitempty" structs:"is_marry"`
}
func BenchmarkStructToMapV1(t *testing.B) {
testS := &testStruct{
Name: "abc",
Age: 0,
}
for i := 0; i < t.N; i++ {
StructToMapV1(testS)
}
}
func BenchmarkStructToMapV2(t *testing.B) {
testS := &testStruct{
Name: "abc",
Age: 0,
}
for i := 0; i < t.N; i++ {
StructToMapV2(testS)
}
}
func BenchmarkStructToMapV3(t *testing.B) {
testS := &testStruct{
Name: "abc",
Age: 0,
}
for i := 0; i < t.N; i++ {
StructToMapV3(testS)
}
}
/*
BenchmarkStructToMapV1-12 1000000 1015 ns/op
BenchmarkStructToMapV2-12 1206841 989.7 ns/op
BenchmarkStructToMapV3-12 588036 1792 ns/op
压测显示V2运行最快
*/
结果
BenchmarkStructToMapV1-12 1000000 1015 ns/op
BenchmarkStructToMapV2-12 1206841 989.7 ns/op
BenchmarkStructToMapV3-12 588036 1792 ns/op
发现最快的是第二种方法。其实三种方法都使用了反射。
第一种方法序列化和反序列化,两次使用反射,没有太大必要。第三种方法需要使用指定的tag,有时候需要转换的结构体是第三方工具生成的,比如gorm或者rpc生成的,或者是自己定义的但是没有加structs标签,就不能使用,局限性太大。所以第二种反射方法比较推荐。
代码
https://github.com/ZBIGBEAR/common/blob/master/util/struct_to_map.go