在Golang中,如何将一个结构体转成map? 本文介绍两种方法。第一种是是使用json包解析解码编码。第二种是使用反射,使用反射的效率比较高,代码在这里 。
  
假设有下面的一个结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 func  newUser ()  User   {	name := "user"  	MyGithub := GithubPage{ 		URL:  "https://github.com/liangyaopei" , 		Star: 1 , 	} 	NoDive := StructNoDive{NoDive: 1 } 	dateStr := "2020-07-21 12:00:00"  	date, _ := time.Parse(timeLayout, dateStr) 	profile := Profile{ 		Experience: "my experience" , 		Date:       date, 	} 	return  User{ 		Name:      name, 		Github:    MyGithub, 		NoDive:    NoDive, 		MyProfile: profile, 	} } type  User struct  {	Name      string        `map:"name,omitempty"`          	Github    GithubPage   `map:"github,dive,omitempty"`   	NoDive    StructNoDive `map:"no_dive,omitempty"`       	MyProfile Profile      `map:"my_profile,omitempty"`    } type  GithubPage struct  {	URL  string  `map:"url"`  	Star int     `map:"star"`  } type  StructNoDive struct  {	NoDive int  } type  Profile struct  {	Experience string     `map:"experience"`  	Date       time.Time `map:"time"`  } func  (p Profile)  StructToMap ()  (key string , value interface {})   {	return  "time" , p.Date.Format(timeLayout) } 
 
json包的marshal,unmarshal 先将结构体序列化成[]byte数组,再从[]byte数组序列化成结构体。
1 2 3 data, _ := json.Marshal(&user) m := make (map [string ]interface {}) json.Unmarshal(data, &m) 
 
优势
使用简单 劣势 
效率比较慢 
不能支持一些定制的键,也不能支持一些定制的方法,例如将struct的域展开等。 
 
使用反射 本文实现了使用反射将结构体转成map的方法。通过标签(tag)和反射,将上文示例的newUser()返回的结果转化成下面的一个map。其中包含struct的域的展开,定制化struct的方法。
1 2 3 4 5 6 7 8 9 map [string ]interface {}{	"name" :    "user" , 	"no_dive" : StructNoDive{NoDive: 1 },      	"url" :     "https://github.com/liangyaopei" , 	"star" :    1 ,      	"time" :    "2020-07-21 12:00:00" , } 
 
实现思路 & 源码解析 1.标签识别。 使用readTag方法读取域(field)的标签,如果没有标签,使用域的名字。然后读取tag中的选项。目前支持3个选项
‘-‘:忽略当前这个域 
‘omitempty’ : 当这个域的值为空,忽略这个域 
‘dive’ : 递归地遍历这个结构体,将所有字段作为键 
 
如果选中了一个选项,就讲这个域对应的二进制位置为1.。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 const  (	OptIgnore    = "-"  	OptOmitempty = "omitempty"  	OptDive      = "dive"  ) const  (	flagIgnore = 1  << iota  	flagOmiEmpty 	flagDive ) func  readTag (f reflect.StructField, tag string )  (string , int )   {	val, ok := f.Tag.Lookup(tag) 	fieldTag := ""  	flag := 0  	 	if  !ok { 		return  f.Name, flag 	} 	opts := strings.Split(val, "," ) 	fieldTag = opts[0 ] 	for  i := 1 ; i < len (opts); i++ { 		switch  opts[i] { 		case  OptIgnore: 			flag |= flagIgnore 		case  OptOmitempty: 			flag |= flagOmiEmpty 		case  OptDive: 			flag |= flagDive 		} 	} 	return  fieldTag, flag } 
 
2.结构体的域(field)的遍历。 遍历结构体的每一个域(field),判断field的类型(kind)。如果是string,int等的基本类型,直接取值,并且把标签中的值作为key。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for  i := 0 ; i < t.NumField(); i++ {        ...         switch  fieldValue.Kind() { 		case  reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: 			res[tagVal] = fieldValue.Int() 		case  reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: 			res[tagVal] = fieldValue.Uint() 		case  reflect.Float32, reflect.Float64: 			res[tagVal] = fieldValue.Float() 		case  reflect.String: 			res[tagVal] = fieldValue.String() 		case  reflect.Bool: 			res[tagVal] = fieldValue.Bool() 		default : 		}     } } 
 
3.内嵌结构体的转换 如果是结构体,先检查有没有实现传入参数的方法,如果实现了,就调用这个方法。如果没有实现,就递归地调用StructToMap方法,然后根据是否展开(dive),来把返回结果写入res的map。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 for  i := 0 ; i < t.NumField(); i++ {		fieldType := t.Field(i) 		 		if  fieldType.PkgPath != ""  { 			continue  		} 		 		tagVal, flag := readTag(fieldType, tag) 		if  flag&flagIgnore != 0  { 			continue  		} 		fieldValue := v.Field(i) 		if  flag&flagOmiEmpty != 0  && fieldValue.IsZero() { 			continue  		} 		 		if  fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { 			continue  		} 		if  fieldValue.Kind() == reflect.Ptr { 			fieldValue = fieldValue.Elem() 		} 		 		switch  fieldValue.Kind() { 		case  reflect.Struct: 			_, ok := fieldValue.Type().MethodByName(methodName) 			if  ok { 				key, value, err := callFunc(fieldValue, methodName) 				if  err != nil  { 					return  nil , err 				} 				res[key] = value 				continue  			} 			 			deepRes, deepErr := StructToMap(fieldValue.Interface(), tag, methodName) 			if  deepErr != nil  { 				return  nil , deepErr 			} 			if  flag&flagDive != 0  { 				for  k, v := range  deepRes { 					res[k] = v 				} 			} else  { 				res[tagVal] = deepRes 			} 		default : 		}     }     ... } func  callFunc (fv reflect.Value, methodName string )  (string , interface {}, error)   {	methodRes := fv.MethodByName(methodName).Call([]reflect.Value{}) 	if  len (methodRes) != methodResNum { 		return  "" , nil , fmt.Errorf("wrong method %s, should have 2 output: (string,interface{})" , methodName) 	} 	if  methodRes[0 ].Kind() != reflect.String { 		return  "" , nil , fmt.Errorf("wrong method %s, first output should be string" , methodName) 	} 	key := methodRes[0 ].String() 	return  key, methodRes[1 ], nil  } 
 
4.array,slice类型的转换 如果是array,slice类型,类似地,检查有没有实现传入参数的方法,如果实现了,就调用这个方法。如果没有实现,将这个field的tag作为key,域的值作为value。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 switch  fieldValue.Kind() {		case  reflect.Slice, reflect.Array: 			_, ok := fieldValue.Type().MethodByName(methodName) 			if  ok { 				key, value, err := callFunc(fieldValue, methodName) 				if  err != nil  { 					return  nil , err 				} 				res[key] = value 				continue  			}             res[tagVal] = fieldValue             .... } 
 
5.其他类型 对于其他类型,例如内嵌的map,直接将其返回结果的值。
1 2 3 4 5 6 7 8 9 10 switch  fieldValue.Kind() {		... 		case  reflect.Map: 			res[tagVal] = fieldValue 		case  reflect.Chan: 			res[tagVal] = fieldValue 		case  reflect.Interface: 			res[tagVal] = fieldValue.Interface() 		default : 		}