Go语言 使用reflect,如何设置结构体字段的值?

mspsb9vt  于 2023-05-20  发布在  Go
关注(0)|答案(3)|浏览(165)

使用reflect包处理struct字段时遇到了一些困难。特别是,还没有弄清楚如何设置字段值。

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
1.获取字段i的值作为a)interface{},B)int -这似乎可以工作
var iface interface{} = reflect.ValueOf(r).Field(i).Interface()
var i int = int(reflect.ValueOf(r).Field(i).Int())
1.设置字段i的值- try one - panic
reflect.ValueOf(r).Field(i).SetInt( i64 )

  • panic*:reflect.Value·SetInt使用未导出字段获取的值

假设它不喜欢字段名“id”和“name”,因此重命名为“Id”和“Name”
(1)这个假设是否正确?
B)如果正确,则认为没有必要,因为在同一文件/包中
1.设置字段i的值-尝试两个(字段名称大写)- panic
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 )
他还记录了字段名称必须是可导出的(以大写字母开始)

f4t66c6m

f4t66c6m1#

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是可寻址的并且不是通过使用未导出的结构字段获得时,才可以更改它。如果CanSet返回false,调用Set或任何类型特定的setter(例如SetBool,SetInt64)将死机。
我们需要确保我们可以Setstruct字段。比如说

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包。软件包文档在“软件包文件”标题下提供了指向源代码文件的链接。

u2nhd7ah

u2nhd7ah2#

这似乎起作用:

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
1u4esq0p

1u4esq0p3#

你可以创建一个helper函数来设置值:

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

Playground:https://go.dev/play/p/uHoy5n3k1DW

相关问题