反射三部曲2·go反射实现结构体间赋值/不同结构体转换

干的不能再干了,如有帮助,感谢留下足迹哦!

目录

背景

情景1:按属性顺序匹配

情景2:按属性名匹配

情景3:按tag匹配

情景4:对于一些同意义的属性,但在两个结构体中的属性类型不同


 

背景

1,结构体有几十个甚至更多字段,往往因为不是同一个结构体而写几十行代码进行转换,a-->b
2,虽然是两个意义完全一样的结构体,但仅因为个别类型不同导致结构体间需进行转换,从而产生了只用来转换字段的几十行甚至更多的代码
3,又如需要通过源结构体tag来作为值挨个赋值给目标结构体的对应字段,由于顺序之类的因素为对不上,最常见的比如:
b.name = a.Name
b.age = a.age

b.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:"姓名"`
}

情景1:按属性顺序匹配

属性名不同、属性顺序相同,需要两个结构体赋值的属性顺序一致。

单个对象转换:

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}]


情景2:按属性名匹配

属性顺序不同、属性名相同

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
}


情景3:按tag匹配

属性顺序不同、属性名不同,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:对于一些同意义的属性,但在两个结构体中的属性类型不同

此时焦点是属性类型,其它的无所谓了,因为前面三种类型都已见到过,此时假设属性类型和顺序不同,通过属性名匹配。


// 情景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、值等

 

 

你可能感兴趣的:(go实战大满贯,java,开发语言)