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 相关文章推荐
Go语言带缓冲的通道实现
Apr 26 Golang
Golang 实现超大文件读取的两种方法
Apr 27 Golang
golang 实现两个结构体复制字段
Apr 28 Golang
解决golang post文件时Content-Type出现的问题
May 02 Golang
go语言基础 seek光标位置os包的使用
May 09 Golang
go语言使用Casbin实现角色的权限控制
Jun 26 Golang
golang中字符串MD5生成方式总结
Jul 04 Golang
Golang 字符串的常见操作
Apr 19 Golang
Go Grpc Gateway兼容HTTP协议文档自动生成网关
Jun 16 Golang
Go微服务项目配置文件的定义和读取示例详解
Jun 21 Golang
Go gorilla/sessions库安装使用
Aug 14 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
逐步提升php框架的性能
2008/01/10 PHP
php遍历目录viewDir函数
2009/12/15 PHP
php多个文件及图片上传实例详解
2014/11/10 PHP
PHPExcel内存泄漏问题解决方法
2015/01/23 PHP
PHP获取input输入框中的值去数据库比较显示出来
2016/11/16 PHP
php多进程应用场景实例详解
2019/07/22 PHP
$()JS小技巧
2007/07/21 Javascript
jQuery中将函数赋值给变量的调用方法
2012/03/23 Javascript
基于pthread_create,readlink,getpid等函数的学习与总结
2013/07/17 Javascript
js字符串日期yyyy-MM-dd转化为date示例代码
2014/03/06 Javascript
javascript 闭包详解
2015/02/15 Javascript
JavaScript 匿名函数和闭包介绍
2015/04/13 Javascript
使用jQuery或者原生js实现鼠标滚动加载页面新数据
2016/03/06 Javascript
js实现内容显示并使用json传输数据
2016/03/16 Javascript
快速掌握jQuery插件WebUploader文件上传
2016/11/07 Javascript
深入理解vue2.0路由如何配置问题
2017/07/18 Javascript
JavaScript-定时器0~9抽奖系统详解(代码)
2017/08/16 Javascript
微信小程序实现图片上传功能实例(前端+PHP后端)
2018/01/10 Javascript
AngularJS实现动态切换样式的方法分析
2018/06/26 Javascript
JS利用prototype给类添加方法操作详解
2019/06/21 Javascript
vue移动端模态框(可传参)的实现
2019/11/20 Javascript
把pandas转换int型为str型的方法
2019/01/29 Python
Python实现12306火车票抢票系统
2019/07/04 Python
Flask中endpoint的理解(小结)
2019/12/11 Python
pycharm运行程序时看不到任何结果显示的解决
2020/02/21 Python
在Django中预防CSRF攻击的操作
2020/03/13 Python
Python CSS选择器爬取京东网商品信息过程解析
2020/06/01 Python
css3实现超炫风车特效
2014/11/12 HTML / CSS
基于DOM+CSS3实现OrgChart组织结构图插件
2016/03/02 HTML / CSS
The North Face北面法国官网:美国著名户外品牌
2019/11/01 全球购物
2014年乡镇个人工作总结
2014/12/03 职场文书
年度考核表个人总结
2015/03/06 职场文书
出纳岗位职责范本
2015/03/31 职场文书
欠条样本
2015/07/03 职场文书
php将xml转化对象的实例详解
2021/11/17 PHP
将MySQL的表数据全量导入clichhouse库中
2022/03/21 MySQL