使用reflect,如何设置结构字段的值?


106

在使用reflect包处理结构字段时遇到了麻烦。特别是,还没有弄清楚如何设置字段值。

输入t struct {fi int; fs字符串}
var rt = t {123,“ jblow”}
var i64 int64 = 456
  1. 获取字段i的名称-这似乎起作用

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

  2. 以a)接口{},b)int的形式获取字段i的值-这似乎可行

    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 )

    恐慌:使用未导出字段获得的值来反映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 )

    恐慌:使用不可寻址的值体现Reflect.Value·SetInt


@peterSO在下面的说明是详尽而高质量的

四。这有效:

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

他还证明字段名称必须是可导出的(以大写字母开头)


我可以用找到的人最接近的例子reflect,以一组数据是comments.gmane.org/gmane.comp.lang.go.general/35045,但即使在那里,他用json.Unmarshal做实际的肮脏的工作
CC年轻

(以上评论已过时)
cc年轻的

Answers:


156

Go是开放源代码。了解反射的一个好方法是查看Go核心开发人员如何使用它。例如,Go fmtjson包。软件包文档在“软件包文件”标题下具有指向源代码文件的链接。

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。值只能是可寻址的并且不能通过使用未导出的结构字段获得,才能更改。如果CanSet返回false,则调用Set或任何特定于类型的setter(例如SetBool,SetInt64)都会感到恐慌。

我们需要确保我们能做到Set这一点struct。例如,

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)
}

5
放弃!在某个地方有一个答案,但是对json pkg的四个小时的工作却没有解决。关于reflect pkg,提取信息非常简单,但是设置数据需要一些黑魔法,我希望在某个地方看到一个简单的示例!
cc年轻的

2
优秀!如果您曾经在泰国,请让我喝一两三杯啤酒!非常感谢你
CC年轻

5
大实际的例子,这篇文章完全揭秘这对我golang.org/doc/articles/laws_of_reflection.html
danmux

很棒的例子。这里是同一代码play.golang.org/p/RK8jR_9rPh的
Sarath Sadasivan Pillai

这不会设置结构字段。这将在指向struct的指针中设置一个字段。显然,这不能回答OP提出的问题。
wvxvw

13

这似乎可行:

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

谢谢!现在,我已经阅读了peterSO的笔记,这很有意义。我使用的是foo,而不是&foo,因此无法更改,并且不确定Elem()的含义。
cc年轻的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.