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
解决golang在import自己的包报错的问题
Apr 29 Golang
Go标准容器之Ring的使用说明
May 05 Golang
Golang 实现获取当前函数名称和文件行号等操作
May 08 Golang
go 实现简易端口扫描的示例
May 22 Golang
go语言使用Casbin实现角色的权限控制
Jun 26 Golang
使用GO语言实现Mysql数据库CURD的简单示例
Aug 07 Golang
Golang原生rpc(rpc服务端源码解读)
Apr 07 Golang
Go语言grpc和protobuf
Apr 13 Golang
golang生成并解析JSON
Apr 14 Golang
golang使用map实现去除重复数组
Apr 14 Golang
GO语言异常处理分析 err接口及defer延迟
Apr 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生成QRcode实例
2014/09/22 PHP
浅析php如何实现App常用的秒发功能
2016/08/03 PHP
中高级PHP程序员应该掌握哪些技术?
2016/09/23 PHP
php给数组赋值的实例方法
2019/09/26 PHP
laravel框架上传图片实现实时预览功能
2019/10/14 PHP
兼容ie和firefox js关闭代码
2008/12/11 Javascript
javascript attachEvent和addEventListener使用方法
2009/03/19 Javascript
常见JS效果之图片减速度滚动实现代码
2011/12/08 Javascript
js将当前时间格式转换成时间搓(自写)
2013/09/26 Javascript
javascript读取Xml文件做一个二级联动菜单示例
2014/03/17 Javascript
关闭页面时window.location事件未执行的原因分析及解决方案
2014/09/01 Javascript
一个JavaScript递归实现反转数组字符串的实例
2014/10/14 Javascript
jQuery中[attribute=value]选择器用法实例
2014/12/31 Javascript
jQuery右侧选项卡焦点图片轮播特效代码分享
2015/09/05 Javascript
几种响应式文字详解
2017/05/19 Javascript
angular-tree-component的使用详解
2018/07/30 Javascript
基于better-scroll 实现歌词联动功能的代码
2020/05/07 Javascript
详解如何修改 node_modules 里的文件
2020/05/22 Javascript
JavaScript数组排序的六种常见算法总结
2020/08/18 Javascript
微信小程序组件生命周期的踩坑记录
2021/03/03 Javascript
[36:14]DOTA2上海特级锦标赛D组小组赛#1 EG VS COL第二局
2016/02/28 DOTA
[02:03]风行者至宝清风环佩外观展示
2020/09/05 DOTA
用Python的urllib库提交WEB表单
2009/02/24 Python
Python命令行参数解析模块getopt使用实例
2015/04/13 Python
Python3学习笔记之列表方法示例详解
2017/10/06 Python
详解pandas的外部数据导入与常用方法
2019/05/01 Python
django中瀑布流写法实例代码
2019/10/14 Python
CSS3制作炫酷的下拉菜单及弹起式选单的实例分享
2016/05/17 HTML / CSS
css3 pointer-events 介绍详解
2017/09/18 HTML / CSS
驴妈妈旅游网:中国新型的B2C旅游电子商务网站
2016/08/16 全球购物
乡文化站暑期培训方案
2014/08/28 职场文书
小学教师学习党的群众路线教育实践活动心得体会
2014/10/31 职场文书
2014年科室工作总结范文
2014/12/19 职场文书
2016年中学法制宣传日活动总结
2016/04/01 职场文书
想创业成功,需要掌握这些要点
2019/12/06 职场文书
Java 多线程协作作业之信号同步
2022/05/11 Java/Android