reflect 反射访问修改结构体的非导出字段

如果字段名的首字母小写,这个字段就是不可导出的。但其实,通过golang提供的反射,我们是可以在包外访问并操作到结构体中的非导出字段的。



package aboutGoTest



type Student struct {
name string
Age int
}



var TestStudent = Student{
name: “jack”,
Age: 12,
}

取地址访问/修改



package main



import (
at “aboutGoTest”
“fmt”
“reflect”
)



func main() {



fmt.Printf("testStudent.name: %v\n", getUnExportedField(&at.TestStudent, "name"))


}



func getUnExportedField(ptr interface{}, fieldName string) reflect.Value {
v := reflect.ValueOf(ptr).Elem().FieldByName(fieldName)
return v
}
可见,通过反射机制可以成功取出name字段。
这里,通过reflect.Value.Elem.FieldByName获取的v是非导出字段反射对象。
注意,reflect.Value.Elem需要reflect.Value的类型必须是interface或者ptr。对于前者,返回interface底层的值;对于后者,返回ptr指向的值。



但是这样获取到的值是不可以修改的,如果要修改name字段,需要用到reflect.NewAt函数,这个函数通过一个类型值的底层地址(指针 p)和类型,返回指向该值的一个指针,这个返回值是可寻址的,即可通过它直接访问该值。
具体的修改是使用reflect.value.Set函数。



package main



import (
at “aboutGoTest”
“fmt”
“reflect”
“unsafe”
)



func main() {



fmt.Printf("testStudent.name: %v\n", getUnExportedField(&at.TestStudent, "name"))

if err := setUnExportedStrField(&at.TestStudent, "name", "kangkang"); err != nil {
fmt.Println("err: %v", err)
}

fmt.Printf("testStudent.name: %v\n", getUnExportedField(&at.TestStudent, "name")) }


func getUnExportedField(ptr interface{}, fieldName string) reflect.Value {
v := reflect.ValueOf(ptr).Elem().FieldByName(fieldName)
return v
}



func setUnExportedStrField(ptr interface{}, fieldName string, newFieldVal interface{}) (err error) {
// 获取非导出字段反射对象
v := reflect.ValueOf(ptr).Elem().FieldByName(fieldName)
// 获取非导出字段可寻址反射对象
// 与上面的区别是:这个是可寻址的
v = reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr())).Elem()



nv := reflect.ValueOf(newFieldVal)
if v.Kind() != nv.Kind() {
return fmt.Errorf("expected kind %v, got kind: %v", v.Kind(), nv.Kind())
}
v.Set(nv)
return nil } 结果: testStudent.name: jack testStudent.name: kangkang


不取地址访问
上面介绍的方法都是要传入指针的,事实上不取地址也是可以访问非导出字段的
代码:
package main



import (
at “aboutGoTest”
“fmt”
“reflect”
)



func main() {



fmt.Printf("testStudent.name: %v\n", getUnExportedField(at.TestStudent, "name")


}



func getUnExportedField(source interface{}, fieldName string) (ret reflect.Value) {
v := reflect.ValueOf(source)



vptr := reflect.New(v.Type()).Elem()
vptr.Set(v)

ret = vptr.FieldByName(fieldName)
return } 这里使用了reflect.New方法,与reflect.NewAt方法不同,这个方法基于指定类型创建一个可以表示该类型的指针,因此这里实际上是在函数内部构造了一个指针,所以外部不用再取地址; 不取地址的修改是反应不到原结构体的,因为在传参的时候是值拷贝


package main



import (
at “aboutGoTest”
“fmt”
“reflect”
“unsafe”
)



func main() {



fmt.Printf("testStudent.name: %v\n", getUnExportedField(at.TestStudent, "name"))

if ret, err := setUnExportedField(at.TestStudent, "name", "kangkang"); err != nil {
fmt.Println("err: %v", err)
} else {

fmt.Printf("testStudent.name: %v\n", getUnExportedField(at.TestStudent, "name"))

v := ret.Interface().(at.Student)
fmt.Printf("ret.name: %v\n", getUnExportedField(v, "name"))

}


}



func getUnExportedField(source interface{}, fieldName string) (ret reflect.Value) {
v := reflect.ValueOf(source)



vptr := reflect.New(v.Type()).Elem()
vptr.Set(v)

ret = vptr.FieldByName(fieldName)
return }


func setUnExportedField(source interface{}, fieldName string, newFieldVal interface{}) (reflect.Value, error) {
v := reflect.ValueOf(source)



vptr := reflect.New(v.Type()).Elem()
vptr.Set(v)

tv := vptr.FieldByName(fieldName)
tv = reflect.NewAt(tv.Type(), unsafe.Pointer(tv.UnsafeAddr())).Elem()

nv := reflect.ValueOf(newFieldVal)
if tv.Kind() != nv.Kind() {
return v, fmt.Errorf("expected kind %v, got kind: %v", v.Kind(), nv.Kind())
}
tv.Set(nv)
return vptr, nil } https://blog.csdn.net/somanlee/article/details/108304525


https://blog.csdn.net/fangkang7/article/details/105045514



https://studygolang.com/articles/12348?fr=sidebar


Category golang