golang 如何用反射reflect操作结构体


Posted in Golang onApril 28, 2021

背景

需要遍历结构体的所有field

对于exported的field, 动态set这个field的value

对于unexported的field, 通过强行取址的方法来获取该值(tricky?)

思路

下面的代码实现了从一个strct ptr对一个包外结构体进行取值的操作,这种场合在笔者需要用到反射的场合中出现比较多

simpleStrtuctField 函数接受一个结构体指针,因为最后希望改变其值,所以传参必须是指针。然后解引用。

接下来遍历结构体的每个field, exported字段是CanInterface的,对于unexported字段,需要强行取址来获取其值

model.go

package model
type Person struct {
 Name string
 age  int
}
func NewPerson(name string, age int) *Person {
 return &Person{
  Name: name,
  age:  age,
 }
}

main.go

package main
import (
	"github.com/miaomiao3/log"
	"../model"
	"reflect"
	"unsafe"
)
func main() {
	person := model.NewPerson("haha", 12)
	log.Debug("before:%+v", person)
	simpleStrtuctField(person)
	simpleStrtuctField(person)
	log.Debug("after:%+v", person)
}
// get unexported field
func simpleStrtuctField(v interface{}) {
	dataType := reflect.TypeOf(v)
	dataValue := reflect.ValueOf(v)
	if dataType.Kind() == reflect.Ptr {
		if dataValue.IsNil() {
			panic("nil ptr")
		}
		// 如果是指针,则要判断一下是否为struct
		originType := reflect.ValueOf(v).Elem().Type()
		if originType.Kind() != reflect.Struct {
			return
		}
		// 解引用
		dataValue = dataValue.Elem()
		dataType = dataType.Elem()
	} else {
		panic("non ptr")
	}
	num := dataType.NumField()
	for i := 0; i < num; i++ {
		field := dataType.Field(i)
		fieldName := field.Name
		fieldValue := dataValue.FieldByName(fieldName)
		if !fieldValue.IsValid() {
			continue
		}
		if fieldValue.CanInterface() {
			log.Debug("exported fieldName:%v value:%v", fieldName, fieldValue.Interface())
			if fieldValue.CanSet() && fieldValue.Kind() == reflect.String {
				oldValue := fieldValue.Interface().(string)
				fieldValue.SetString(oldValue + " auto append")
			}
		} else {
			// 强行取址
			forceValue := reflect.NewAt(fieldValue.Type(), unsafe.Pointer(fieldValue.UnsafeAddr())).Elem()
			log.Debug("unexported fieldName:%v value:%v", fieldName, forceValue.Interface())
		}
	}
}

output:

2019/06/02 17:15:31.64 [D] before:&{Name:haha age:12}

2019/06/02 17:15:31.64 [D] exported fieldName:Name value:haha

2019/06/02 17:15:31.64 [D] unexported fieldName:age value:12

2019/06/02 17:15:31.64 [D] after:&{Name:haha auto append age:12}

可以看到,Name字段被反射改变了,age的值也已经获取到

补充:go语言通过反射创建结构体、赋值、并调用对应方法

看代码吧~

package main
import (
	"fmt"
	"reflect"
	"testing"
)
type Call struct {
	Num1 int
	Num2 int
}
func (call Call) GetSub(name string){
	fmt.Printf("%v 完成了减法运算,%v - %v = %v \n", name, call.Num1, call.Num2, call.Num1 - call.Num2)
}
func (call *Call) GetSum(name string){
	fmt.Printf("%v 完成了加法运算,%v + %v = %v \n", name, call.Num1, call.Num2, call.Num1 + call.Num2)
}
func TestReflect(t *testing.T) {
	var (
		call *Call
		rValues []reflect.Value
		rValues2 []reflect.Value
	)
	ptrType := reflect.TypeOf(call) //获取call的指针的reflect.Type
	trueType := ptrType.Elem() //获取type的真实类型
	ptrValue := reflect.New(trueType) //返回对象的指针对应的reflect.Value
	call = ptrValue.Interface().(*Call)
	trueValue := ptrValue.Elem() //获取真实的结构体类型
	trueValue.FieldByName("Num1").SetInt(123)//设置对象属性,注意这个一定要是真实的结构类型的reflect.Value才能调用,指针类型reflect.Value的会报错
	//ptrValue.FieldByName("Num2").SetInt(23)
	trueValue.FieldByName("Num2").SetInt(23)
	//rValues = make([]reflect.Value, 0)
	rValues = append(rValues, reflect.ValueOf("xiaopeng"))//调用对应的方法
	fmt.Println(rValues)
	trueValue.MethodByName("GetSub").Call(rValues)
	/*
	fixme 在反射中,指针的方法不可以给实际类型调用,实际类型的方法可以给指针类型调用,因为go语言对这种操作做了封装
	所以下面一句是没问题的
	下下一句会运行时报错
	 */
	//ptrValue.MethodByName("GetSub").Call(rValues)
	//trueValue.MethodByName("GetSum").Call(append(rValues2, reflect.ValueOf("hiram")))
	ptrValue.MethodByName("GetSum").Call(append(rValues2, reflect.ValueOf("hiram")))
	fmt.Println(call)
	
	/*
	fixme 在实际使用中  指针和实体都能相互转换,不会影响调用
	但是指针的方法在方法体内的操作会影响到结构体本身属性
	而实体的方法不会,因为go对于结构体、数组、基本类型都是值传递
	 */
	call.GetSub("aaa")
	(*call).GetSub("bbb")
	call.GetSum("ccc")
	(*call).GetSum("ddd")
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
golang如何去除多余空白字符(含制表符)
Apr 25 Golang
go结构体嵌套的切片数组操作
Apr 28 Golang
Go使用协程交替打印字符
Apr 29 Golang
使用Golang的channel交叉打印两个数组的操作
Apr 29 Golang
goland设置颜色和字体的操作
May 05 Golang
完美解决golang go get私有仓库的问题
May 05 Golang
Golang全局变量加锁的问题解决
May 08 Golang
Go语言空白表示符_的实例用法
Jul 04 Golang
golang fmt格式“占位符”的实例用法详解
Jul 04 Golang
Go并发4种方法简明讲解
Apr 06 Golang
Golang流模式之grpc的四种数据流
Apr 13 Golang
Golang解析JSON对象
Apr 30 Golang
golang 生成对应的数据表struct定义操作
Apr 28 #Golang
golang 如何通过反射创建新对象
Apr 28 #Golang
golang 实现两个结构体复制字段
Apr 28 #Golang
go结构体嵌套的切片数组操作
Apr 28 #Golang
golang json数组拼接的实例
Apr 28 #Golang
golang 实现对Map进行键值自定义排序
Apr 28 #Golang
go语言中json数据的读取和写出操作
Apr 28 #Golang
You might like
main.php
2006/12/09 PHP
使用新浪微博API的OAuth认证发布微博实例
2015/03/27 PHP
Add a Formatted Table to a Word Document
2007/06/15 Javascript
javascripit实现密码强度检测代码分享
2013/12/12 Javascript
javascript轻松实现当鼠标移开时已弹出子菜单自动消失
2013/12/29 Javascript
javascript操作数组详解
2014/12/17 Javascript
JavaScript中number转换成string介绍
2014/12/31 Javascript
jQuery中trigger()方法用法实例
2015/01/19 Javascript
简述AngularJS的控制器的使用
2015/06/16 Javascript
javascript中对变量类型的判断方法
2015/08/09 Javascript
JavaScript与HTML的结合方法详解
2015/11/23 Javascript
利用vue-router实现二级菜单内容转换
2016/11/30 Javascript
Jquery Easyui搜索框组件SearchBox使用详解(19)
2016/12/17 Javascript
支持移动端原生js轮播图
2017/02/16 Javascript
Angular2中select用法之设置默认值与事件详解
2017/05/07 Javascript
vue-scroller记录滚动位置的示例代码
2018/01/17 Javascript
jQuery实现动态添加和删除input框实例代码
2019/03/26 jQuery
JavaScript实现点击出现子菜单效果
2021/02/08 Javascript
Python生成随机MAC地址
2015/03/10 Python
python通过cookie模拟已登录状态的初步研究
2016/11/09 Python
基于Python的关键字监控及告警
2017/07/06 Python
PyQt5实现拖放功能
2018/04/25 Python
对python内置map和six.moves.map的区别详解
2018/12/19 Python
Python + OpenCV 实现LBP特征提取的示例代码
2019/07/11 Python
Python 实现取多维数组第n维的前几位
2019/11/26 Python
零基础学python应该从哪里入手
2020/08/11 Python
python实现模拟器爬取抖音评论数据的示例代码
2021/01/06 Python
美国气象仪器、花园装饰和墙壁艺术商店:Wind & Weather
2019/05/29 全球购物
Hobbs官方网站:英国奢华女性时尚服装
2020/02/22 全球购物
银行爱岗敬业演讲稿
2014/05/05 职场文书
乔丹名人堂演讲稿
2014/05/24 职场文书
大三学年自我鉴定范文(3篇)
2014/09/28 职场文书
某学校的2019年度工作报告范本
2019/10/11 职场文书
Python离线安装openpyxl模块的步骤
2021/03/30 Python
图解排序算法之希尔排序Java实现
2021/06/26 Java/Android
Python函数对象与闭包函数
2022/04/13 Python