使用反射,如何设置结构体字段的值?

141

使用reflect包处理结构体字段时遇到了一些问题,尤其是没有弄清如何设置字段值。

type t struct { fi int; fs string }
var r t = t{ 123, "jblow" }
var i64 int64 = 456
  1. 获取第i个字段的名称 - 这似乎可以工作

    var field = reflect.TypeOf(r).Field(i).Name

  2. 将第i个字段的值作为a) interface{},b) int获取 - 这似乎可以工作

    var iface interface{} = reflect.ValueOf(r).Field(i).Interface()

    var i int = int(reflect.ValueOf(r).Field(i).Int())

  3. 设置第i个字段的值 - 尝试一 - 引发恐慌

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    panic: reflect.Value·SetInt使用使用未公开字段获得的值

    假设它不喜欢字段名称"id"和"name",因此重命名为"Id"和"Name"

    a)这个假设正确吗?

    b)如果正确,是否认为不需要重命名,因为在同一个文件/包中

  4. 设置第i个字段的值 - 尝试二(使用大写字段名称) - 引发恐慌

    reflect.ValueOf(r).Field(i).SetInt( 465 )

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    panic: reflect.Value·SetInt使用不可寻址的值


@peterSO提供了详细和高质量的以下说明:

四. 这个可以工作:

reflect.ValueOf(&r).Elem().Field(i).SetInt( i64 )

他还记录了字段名称必须是可导出的(以大写字母开头)。


我能找到的最接近使用reflect设置数据的例子是http://comments.gmane.org/gmane.comp.lang.go.general/35045,但即使在那里,他也使用了`json.Unmarshal`来完成实际的工作。 - cc young
1
(上述评论已过时) - cc young
3个回答

206
Go的json包可以将JSON编组和解组到Go结构中。
下面是一个逐步示例,它在小心避免错误的同时设置了struct字段的值。
Go reflect 包有一个CanAddr函数。
func (v Value) CanAddr() bool

如果可以使用Addr获取值的地址,则CanAddr返回true。这些值被称为可寻址。如果一个值是切片的元素、可寻址数组的元素、可寻址结构体的字段或解引用指针的结果,则该值是可寻址的。如果CanAddr返回false,则调用Addr将导致恐慌。

Go reflect包有一个CanSet函数,如果返回的结果为true,则意味着CanAddr也是true

func (v Value) CanSet() bool

如果v的值可以被更改,CanSet会返回true。只有当Value是可寻址的并且不是通过未公开的struct字段获得时,才能更改Value的值。如果CanSet返回false,则调用Set或任何类型特定的setter(例如SetBool、SetInt64)将导致panic。

我们需要确保我们可以Set结构体字段。例如,

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    // N at start
    fmt.Println(n.N)
    // pointer to struct - addressable
    ps := reflect.ValueOf(&n)
    // struct
    s := ps.Elem()
    if s.Kind() == reflect.Struct {
        // exported field
        f := s.FieldByName("N")
        if f.IsValid() {
            // A Value can be changed only if it is 
            // addressable and was not obtained by 
            // the use of unexported struct fields.
            if f.CanSet() {
                // change value of N
                if f.Kind() == reflect.Int {
                    x := int64(7)
                    if !f.OverflowInt(x) {
                        f.SetInt(x)
                    }
                }
            }
        }
    }
    // N at end
    fmt.Println(n.N)
}

Output:
42
7

如果我们能够确信所有的错误检查都是不必要的,那么这个例子就变得简单了。
package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    fmt.Println(n.N)
    reflect.ValueOf(&n).Elem().FieldByName("N").SetInt(7)
    fmt.Println(n.N)
}

顺便提一下,Go作为开源代码可用。了解反射的好方法是看看核心Go开发人员如何使用它。例如,Go fmtjson包。包文档在标题“Package files”下提供了链接到源代码文件的链接。


8
放弃吧!在其中有一个答案,但对json包进行了四个小时的工作,我还是没有找到它。关于reflect包,提取信息相当简单,但设置数据需要一些黑魔法,我很想在某处看到一个简单的示例! - cc young
4
太棒了!如果你有机会来泰国,请让我请你喝两三瓶啤酒吧!非常感谢。 - cc young
6
很好的实际例子,这篇文章对我来说深度揭示了它的奥秘 http://golang.org/doc/articles/laws_of_reflection.html - danmux
1
这并没有设置结构体字段。这是在指向结构体的指针中设置字段。显然,这并没有回答OP提出的问题。 - wvxvw
1
补充说明:CanSet 返回 true 的必要条件是结构体的属性必须具有大写前缀 - Carson
显示剩余3条评论

20

这似乎有效:

package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
    Number int
    Text string
}

func main() {
    foo := Foo{123, "Hello"}

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))

    reflect.ValueOf(&foo).Elem().Field(0).SetInt(321)

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))
}

输出:

123
321

1
感谢!现在我已经阅读了PeterSO的笔记,这一切都很清晰明了。我之前使用的是foo,而不是&foo,所以无法更改,并且对Elem()感到困惑。 - cc young

2

您可以创建一个辅助函数来设置值:

package main

import (
    "fmt"
    "log"
    "reflect"
    "time"
)

type Apple struct {
    Color     string
    CreatedAt time.Time
}

func SetValue(obj any, field string, value any) {
    ref := reflect.ValueOf(obj)

    // if its a pointer, resolve its value
    if ref.Kind() == reflect.Ptr {
        ref = reflect.Indirect(ref)
    }

    if ref.Kind() == reflect.Interface {
        ref = ref.Elem()
    }

    // should double check we now have a struct (could still be anything)
    if ref.Kind() != reflect.Struct {
        log.Fatal("unexpected type")
    }

    prop := ref.FieldByName(field)
    prop.Set(reflect.ValueOf(value))
}

func main() {
    myStruct := Apple{}
    SetValue(&myStruct, "Color", "red")
    SetValue(&myStruct, "CreatedAt", time.Now())
    fmt.Printf("Result: %+v\n", myStruct)
}

编程游乐场:https://go.dev/play/p/uHoy5n3k1DW


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接