【问题标题】:Using reflect, how do you set the value of a struct field?使用反射,如何设置结构字段的值?
【发布时间】:2011-06-18 09:24:05
【问题描述】:

在使用 reflect 包处理结构字段时遇到了困难。尤其是还没想好怎么设置字段值。

类型 t 结构 { fi int; fs 字符串 } 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 )

    恐慌: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 )

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

【问题讨论】:

标签: go reflection go-reflect


【解决方案1】:

Go json 包从 Go 结构编组和解组 JSON。

这是一个分步示例,它设置struct 字段的值,同时小心避免错误。

Go reflect 包有一个 CanAddr 函数。

func (v Value) CanAddr() bool

CanAddr 如果值是 地址可以通过 Addr 获得。 这样的值被称为可寻址的。一种 值是可寻址的,如果它是 切片的元素,一个元素 可寻址数组,一个字段 可寻址结构,或结果 取消引用一个指针。如果可以添加 返回 false,调用 Addr 将 恐慌。

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

func (v Value) CanSet() bool

如果 v 的值,CanSet 返回 true 可以更改。值可以更改 只有当它是可寻址的并且不是 通过使用未出口获得 结构字段。如果 CanSet 返回 false,调用 Set 或任何 特定类型的设置器(例如,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)
}

顺便说一句,Go 可以通过 open source code 获得。了解反射的一个好方法是看看核心 Go 开发人员如何使用它。例如,Go fmtjson 包。包文档在标题包文件下有指向源代码文件的链接。

【讨论】:

  • 放弃!某处有一个答案,但是 json pkg 上的四个小时的工作并没有给我带来它。重新反射 pkg,提取信息非常简单,但设置数据需要一些黑魔法,我希望在某个地方看到一个简单的例子!
  • 优秀!如果你曾经在泰国,请让我请你喝一两三杯啤酒!非常感谢
  • 很好的实际例子,这篇文章为我彻底揭开了它的神秘面纱golang.org/doc/articles/laws_of_reflection.html
  • 很棒的例子。这是相同代码的游乐场示例play.golang.org/p/RK8jR_9rPh
  • 这不会设置结构字段。这会在指向结构的指针中设置一个字段。显然,这并没有回答 OP 提出的问题。
【解决方案2】:

这似乎有效:

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() 是关于什么的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-01
  • 2018-09-20
  • 1970-01-01
  • 1970-01-01
  • 2019-02-21
  • 2014-07-28
相关资源
最近更新 更多