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

 更新时间:2021年04月28日 15:29:43   作者:皿小草  
这篇文章主要介绍了golang 用反射reflect操作结构体的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

背景

需要遍历结构体的所有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")
}

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

相关文章

  • 使用go自定义prometheus的exporter

    使用go自定义prometheus的exporter

    在prometheus中如果要监控服务器和应用的各种指标,需要用各种各样的exporter服务,这篇文章主要介绍了使用go自定义prometheus的exporter,需要的朋友可以参考下
    2023-03-03
  • golang判断chan channel是否关闭的方法

    golang判断chan channel是否关闭的方法

    这篇文章主要介绍了golang判断chan channel是否关闭的方法,结合实例形式对比分析了Go语言判断chan没有关闭的后果及关闭的方法,需要的朋友可以参考下
    2016-07-07
  • GO项目实战之Gorm格式化时间字段实现

    GO项目实战之Gorm格式化时间字段实现

    GORM自带的time.Time类型JSON默认输出RFC3339Nano格式的,下面这篇文章主要给大家介绍了关于GO项目实战之Gorm格式化时间字段实现的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-01-01
  • Golang pipe在不同场景下远程交互

    Golang pipe在不同场景下远程交互

    这篇文章主要介绍了Golang pipe在不同场景下远程交互,pipe实现从一个进程重定向至另一个进程,它是双向数据通道,用于实现进行间通信
    2023-03-03
  • golang 定时任务方面time.Sleep和time.Tick的优劣对比分析

    golang 定时任务方面time.Sleep和time.Tick的优劣对比分析

    这篇文章主要介绍了golang 定时任务方面time.Sleep和time.Tick的优劣对比分析,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • Go语言同步等待组sync.WaitGroup结构体对象方法详解

    Go语言同步等待组sync.WaitGroup结构体对象方法详解

    这篇文章主要为大家介绍了Go语言同步等待组sync.WaitGroup结构体对象方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Go语言metrics应用监控指标基本使用说明

    Go语言metrics应用监控指标基本使用说明

    这篇文章主要为大家介绍了Go语言metrics应用监控指标的基本使用说明,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-02-02
  • 详解Golang 推荐的命名规范

    详解Golang 推荐的命名规范

    这篇文章主要介绍了详解Golang 推荐的命名规范,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-02-02
  • Go高级特性探究之HTTP错误处理详解

    Go高级特性探究之HTTP错误处理详解

    在Web应用程序中,HTTP错误处理是非常重要的,它关系到Web应用程序的稳定性和可靠性,本文介绍如何在Go项目中处理HTTP错误,并提供相应的解决方案和实践经验,希望对Go语言Web应用程序的开发者有所帮助
    2023-06-06
  • golang解析域名的步骤全纪录

    golang解析域名的步骤全纪录

    这篇文章主要给大家介绍了利用golang如何解析域名的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
    2018-12-12

最新评论