干的不能再干了,如有帮助,感谢留下足迹哦!
目录
背景
情景1:按属性顺序匹配
情景2:按属性名匹配
情景3:按tag匹配
情景4:对于一些同意义的属性,但在两个结构体中的属性类型不同
1,结构体有几十个甚至更多字段,往往因为不是同一个结构体而写几十行代码进行转换,a-->b
2,虽然是两个意义完全一样的结构体,但仅因为个别类型不同导致结构体间需进行转换,从而产生了只用来转换字段的几十行甚至更多的代码
3,又如需要通过源结构体tag来作为值挨个赋值给目标结构体的对应字段,由于顺序之类的因素为对不上,最常见的比如:
b.name = a.Name
b.age = a.ageb.gender = a.gender
...
...
...
手困不困。。。需求:要把一个有值的S1对象转换为S2类型的对象,下面分几种场景分别给出解决方案(单个对象、列表对象都有)
本文着力解决掉上述几种情形的问题。
注意:
通过x匹配时,两个结构体的x就要一样,否则没什么意义。(x可能是属性顺序、属性名、tag等等)
如Src中name在age前面,Dist中name在age后面,此时要按照属性的顺序匹配就没什么意义,值就会赋错,还可能报错。
先定义一些长得稍微不一样的结构体,下面会用到:
// 源结构体,主要为说明功能字段少点看起来清楚,假设它有几十个字段
// 属性都是string类型
type Src1 struct {
Name string `cn:"姓名"`
Age string `cn:"年龄"`
}
type Dist1 struct {
Name string `cn:"姓名"`
NianLin string `cn:"年龄"` // 弄个不一样的名称
}
// 情景2中使用
type Dist2 struct {
Name string `cn:"姓名"`
Age string `cn:"年龄"`
}
// Src2和Dist3在情景3中使用
type Src2 struct {
Name string `cn:"姓名"`
Age1 int `cn:"年龄"` // 弄个不一样的名称
}
type Dist3 struct {
Age2 int `cn:"年龄"`
Name string `cn:"姓名"`
}
// 情景4使用
type Dist5 struct {
Age int `cn:"年龄"`
Name string `cn:"姓名"`
}
属性名不同、属性顺序相同,需要两个结构体赋值的属性顺序一致。
单个对象转换:
func convertOne1(s1 Src1) (s2 Dist1) {
s1Type := reflect.TypeOf(Src1{})
//s2Type := reflect.TypeOf(Dist1{})
s2Value := reflect.ValueOf(&s2).Elem()
s1Value := reflect.ValueOf(&s1).Elem() // 传指针
for i := 0; i < s1Type.NumField(); i++ {
s2Value.Field(i).Set(s1Value.Field(i))
}
return
}
列表转换:
func convertList1(s1List []Src1) (distList []Dist1) {
sType := reflect.TypeOf(Src1{})
for _, s1 := range s1List { // 外层循环,扫描每个对象的每个字段的值
s2 := &Dist1{} // 设置值得目标S2对象
s2Value := reflect.ValueOf(s2).Elem() // 取得s2对象的“中枢”
s1Value := reflect.ValueOf(&s1).Elem() // 取得s1对象的“中枢”
for i := 0; i < sType.NumField(); i++ { // 扫描单个s1对象的每个字段
s2Value.Field(i).Set(s1Value.Field(i))
}
distList = append(distList, *s2)
}
return
}
验证:
func main() {
// 先构造s1对象源数据
s11 := Src1{Name: "name1", Age: "99"}
s12 := Src1{Name: "name2", Age: "98"}
s1List := []Src1{s11, s12}
// 单个
dist1 := convertOne1(s11)
fmt.Printf("dist1: %+v\n", dist1)
// 列表
s2List := convertList1(s1List)
fmt.Printf("s1List: %+v, s2List: %+v", s1List, s2List)
}
speed running:
dist1: {Name:name1 NianLin:99}
s1List: [{Name:name1 Age:99} {Name:name2 Age:98}], s2List: [{Name:name1 NianLin:
99} {Name:name2 NianLin:98}]
属性顺序不同、属性名相同
func main() {
s11 := Src1{Name: "name2", Age: "99"}
dist2 := convertOne2(s11)
fmt.Printf("dist2: %+v\n", dist2) // dist2: {Name:name2 Age:99}
}
// 方法2:按属性名称匹配
// 顺序是乱的(S1的Name在S1中第一个位置,但是在S2中是第2个位置)这种情况可通过tag匹配
func convertOne2(s1 Src1) (s2 Dist2) {
s1Type := reflect.TypeOf(Src1{})
//s2Type := reflect.TypeOf(Dist2{})
s2Value := reflect.ValueOf(&s2).Elem()
s1Value := reflect.ValueOf(&s1).Elem()
for i := 0; i < s1Type.NumField(); i++ {
// 方法1 直接以名称拿到
s2Value.FieldByName(s1Type.Field(i).Name).Set(s1Value.Field(i))
// 方法2 便历找跟你名字一样的那个
//for j := 0; j < s2Type.NumField(); j++ {
// if s1Type.Field(i).Name == s2Type.Field(j).Name {
// s2Value.Field(j).Set(s1Value.Field(i))
// }
//}
}
return
}
属性顺序不同、属性名不同,tag相同(tag如果再不相同那就别玩了哈哈)
// 情景3
func main() {
s11 := Src2{Name: "name3", Age1: 99}
dist3 := convertOne3(s11)
fmt.Printf("dist3: %+v\n", dist3) // dist3: {Age2:99 Name:name3}
}
var egTag = "cn"
// 方法3:按tag匹配
func convertOne3(s1 Src2) (s2 Dist3) {
s1Type := reflect.TypeOf(Src2{})
s2Type := reflect.TypeOf(Dist3{})
s2Value := reflect.ValueOf(&s2).Elem()
s1Value := reflect.ValueOf(&s1).Elem()
for i := 0; i < s1Type.NumField(); i++ {
tag1Value := s1Type.Field(i).Tag.Get(egTag) // 拿到s1对象的tag值
for j := 0; j < s2Type.NumField(); j++ {
tag2Value := s2Type.Field(j).Tag.Get(egTag) // 拿到s2对象的tag值
if tag1Value == tag2Value {
s2Value.Field(j).Set(s1Value.Field(i))
}
}
}
return
}
此时焦点是属性类型,其它的无所谓了,因为前面三种类型都已见到过,此时假设属性类型和顺序不同,通过属性名匹配。
// 情景4 Src1-->Dist5, 其中Age属性类型不同
func main() {
s11 := Src1{Name: "name5", Age: "99"}
dist5 := convertOne4(s11)
fmt.Printf("dist5: %+v\n", dist5) // dist5: {Age:99 Name:name5}
}
func convertOne4(s1 Src1) (s2 Dist5) {
s1Type := reflect.TypeOf(Src1{})
s2Type := reflect.TypeOf(Dist5{})
s2Value := reflect.ValueOf(&s2).Elem()
s1Value := reflect.ValueOf(&s1).Elem()
for i := 0; i < s1Type.NumField(); i++ {
// s2Value.FieldByName(s1Type.Field(i).Name).Set(s1Value.Field(i)) // 由于其中有类型不同的,因此不能直接set
// 由于age字段类型不同,因此一直用Set()方法,在类型不同时就会报panic: reflect.Set: value of type string is not assignable to type int
// 采用下面的switch case来针对性区别
for j := 0; j < s2Type.NumField(); j++ {
fieldType2 := s2Type.Field(j).Type.Kind()
fieldType1 := s1Type.Field(i).Type.Kind()
// 先找到相同的字段
if s1Type.Field(i).Name == s2Type.Field(j).Name {
if fieldType1 == fieldType2 { // 类型相同时,直接赋值
s2Value.Field(j).Set(s1Value.Field(i))
break
}
// 类型不同时,按实际情况
switch fieldType2 {
case reflect.String:
s2Value.Field(j).SetString(s1Value.Field(i).String())
case reflect.Int:
// 先拿到原字段的字符串
distStr := s1Value.Field(i).String()
// 按理说既然有转换的需要,则对应字段的字符串值肯定是能转成功的
// 当然也不能想当然,这个错误酌情处理,此处为演示功能实现暂且忽略err
distInt, _ := strconv.Atoi(distStr)
s2Value.Field(j).SetInt(int64(distInt))
}
break
}
}
}
return
}
干的不能再干了,如有帮助,感谢留下足迹哦!
相关好文:
go反射获取结构体字段名、tag、值等