首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Go系列:通过反射来操作结构体

Go系列:通过反射来操作结构体

原创
作者头像
用户9805946
发布2024-11-19 22:58:43
发布2024-11-19 22:58:43
930
举报
文章被收录于专栏:GoGo

在日常开中,总是会有各种trick的需求,Go这种强类型语言按常规写法就不能完成一些需求,所以反射就是一般瑞士军刀,在某些场合可以用简单的方法功能。

每当这个时候,就无比怀恋Python

下面列出一些通过反射操作结构体的常用方法

通过反射修改结构的属性

基本类型

代码语言:go
复制
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
)

type User struct {
	Name string
	Age  int
}

func (u *User) String() string {
	bs, _ := json.Marshal(u)
	return string(bs)
}

func SetStructValueString(obj *User, field string, value string) error {
	rv := reflect.ValueOf(obj)
	if rv.Kind() == reflect.Ptr {
		rv = rv.Elem()
	}

	if !rv.CanSet() {
		return errors.New("obj cannot set")
	}

	f := rv.FieldByName(field)
	if !f.IsValid() {
		return errors.New("field " + field + " not exists")
	}

	f.SetString(value)

	return nil
}

说明:

  • string、int、bool等基本类型比较简单,主要就是先通过findByName找到field,然后再设置
  • 以上是一个设置string属性的例子,其他基本类型基本都是如此

如果成员是基本类型的指针呢

代码语言:go
复制
func SetStructValueStringPtr(obj *User, field string, value string) error {
	rv := reflect.ValueOf(obj)
	if rv.Kind() == reflect.Ptr {
		rv = rv.Elem()
	}

	if !rv.CanSet() {
		return errors.New("obj cannot set")
	}

	f := rv.FieldByName(field)
	if !f.IsValid() {
		return errors.New("field " + field + " not exists")
	}

	f.Set(reflect.ValueOf(&value))

	return nil
}

说明:

  • 区别就在与最后的f.Set和他的参数。这里先获取value的地址,然后创建一个reflect.Value对象,再调用field的Set方法
  • 其他类型的指针都可以这样操作,包括slice、map等

向类型为slice的属性中添加元素

代码语言:go
复制
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
)

type User struct {
	Name     string
	Age      int
	Email    *string
	Siblings []string
}

func (u *User) String() string {
	bs, _ := json.Marshal(u)
	return string(bs)
}

func AppendStructValueSlice(obj *User, field string, values []string) error {
	rv := reflect.ValueOf(obj)
	if rv.Kind() == reflect.Ptr {
		rv = rv.Elem()
	}

	if !rv.CanSet() {
		return errors.New("obj cannot set")
	}

	f := rv.FieldByName(field)
	if !f.IsValid() {
		return errors.New("field " + field + " not exists")
	}

	for _, v := range values {
		f = reflect.Append(f, reflect.ValueOf(v))
	}
	// or用如下方法
	//f = reflect.AppendSlice(f, reflect.ValueOf(values))

	rv.FieldByName(field).Set(f)

	return nil
}
func main() {
	name := "Smith"
	obj := &User{Name: name, Age: 18, Siblings: []string{"Van"}}
	fmt.Println(obj)

	AppendStructValueSlice(obj, "Siblings", []string{"John", "Kelly"})
	fmt.Println(obj)
}

Output:
{"Name":"Smith","Age":18,"Email":null,"Siblings":["Van"]}
{"Name":"Smith","Age":18,"Email":null,"Siblings":["Van","John","Kelly"]}

说明:

从这个例子可以看到,对这种容器类型,获取到的field也可以认为是一个特殊的slice,然后对需要加入的元素依次进行添加,最后因为field已经被重新赋值,那么需要重新执行FieldByName获取field变量的Set方法

那么,如果我们要删除slice中的一个元素呢

也很简单,我们知道如果这个字段时slice,那么f就是可以遍历的,我们先创建一个空的slice value,然后遍历f,过滤掉要删除的值,其余值保存到我们的新的slice value中,最后把这个新的slice value设置到我们的成员上去就可以了

代码语言:go
复制
func DeleteStructValueSlice(obj *User, field string, values string) error {
	rv := reflect.ValueOf(obj)
	if rv.Kind() == reflect.Ptr {
		rv = rv.Elem()
	}

	if !rv.CanSet() {
		return errors.New("obj cannot set")
	}

	f := rv.FieldByName(field)
	if !f.IsValid() {
		return errors.New("field " + field + " not exists")
	}

	v := reflect.ValueOf([]string{})
	for i := 0; i < f.Len(); i++ {
		if f.Index(i).String() == values {
			continue
		}
		v = reflect.Append(v, f.Index(i))
	}

	rv.FieldByName(field).Set(v)

	return nil
}

此外,map类型的对象的修改思路都大概如此,如果有需要再补充吧

TODO: 通过反射调用成员函数

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 通过反射修改结构的属性
    • 基本类型
    • 如果成员是基本类型的指针呢
    • 向类型为slice的属性中添加元素
  • TODO: 通过反射调用成员函数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档