Go 结构体转换成 map


写在前面

最近项目中多次遇到需要将结构体转换成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

参考

[1]结构体转map[string]interface{}的若干方法


文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录