golang 如何用反射reflect操作结构体
程序员文章站
2022-07-08 22:06:52
背景需要遍历结构体的所有field对于exported的field, 动态set这个field的value对于unexported的field, 通过强行取址的方法来获取该值(tricky?)...
背景
需要遍历结构体的所有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") }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。