【问题标题】:reflect runtime error: call of reflect.flag.mustBeAssignable on zero Value反映运行时错误:在零值上调用 reflect.flag.mustBeAssignable
【发布时间】:2020-01-19 09:41:10
【问题描述】:

我正在 go playground 上测试这段代码 sn-p,我的目标是使用反射从一个对象获取字段,然后将值设置为另一个对象

package main

import (
    "fmt"
    "reflect"
)

type T struct {
    A int    `json:"aaa" test:"testaaa"`
    B string `json:"bbb" test:"testbbb"`
}
type newT struct {
    AA int
    BB string
}

func main() {
    t := T{
        A: 123,
        B: "hello",
    }
    tt := reflect.TypeOf(t)
    tv := reflect.ValueOf(t)

    newT := &newT{}
    newTValue := reflect.ValueOf(newT)

    for i := 0; i < tt.NumField(); i++ {
        field := tt.Field(i)
        newTTag := field.Tag.Get("newT")
        tValue := tv.Field(i)
        newTValue.Elem().FieldByName(newTTag).Set(tValue)
    }

    fmt.Println(newT)
}

它给出了一个非常奇怪的错误:

panic: reflect: call of reflect.flag.mustBeAssignable on zero Value

goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x0, 0x0)
    /usr/local/go/src/reflect/value.go:240 +0xe0
reflect.flag.mustBeAssignable(...)
    /usr/local/go/src/reflect/value.go:234
reflect.Value.Set(0x0, 0x0, 0x0, 0x100f80, 0x40a0f0, 0x82)
    /usr/local/go/src/reflect/value.go:1531 +0x40
main.main()
    /tmp/sandbox166479609/prog.go:32 +0x400

Program exited: status 2.

如何解决?

【问题讨论】:

    标签: go set field reflect


    【解决方案1】:

    正如错误 call of reflect.flag.mustBeAssignable on zero Value 所说,newTValue.Elem().FieldByName(newTTag).CanSet() 在您的代码中并根据文档返回 false

    Set 将 x 分配给值 v。如果 CanSet 返回 false,它会恐慌。与 Go 一样,x 的值必须可分配给 v 的类型。

    这是一个更正的代码,它从一个对象中获取字段并将值分配给另一个对象。

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type T struct {
        A int    `json:"aaa" test:"AA"`
        B string `json:"bbb" test:"BB"`
    }
    type newT struct {
        AA int
        BB string
    Testaaa string
    }
    
    func main() {
        t := T{
            A: 123,
            B: "hello",
        }
        tt := reflect.TypeOf(t)
        tv := reflect.ValueOf(t)
    
        newT := &newT{}
        newTValue := reflect.ValueOf(newT)
    
        for i := 0; i < tt.NumField(); i++ {
            field := tt.Field(i)
            newTTag := field.Tag.Get("test")
            tValue := tv.Field(i)
            newTfield := newTValue.Elem().FieldByName(newTTag)
            if newTfield.CanSet() {
                 newTfield.Set(tValue)
            }
        }
    
        fmt.Println(newT)
    }
    

    【讨论】:

    • 好了,这次不慌了,但问题依旧:无法设置(CanSet()返回false)。
    • 在您的代码中,它将返回 false,因为在任何 Ts 字段中都没有名为 newT 的标签,因此 newTTag 是空字符串
    【解决方案2】:

    第一:

    for i := 0; i < tt.NumField(); i++ {
        field := tt.Field(i)
    

    这里的每个步骤都会遍历T 类型实例的字段。所以这些字段将是A——或者更确切地说,NameA 的字段描述符,它描述了一个带有 json 和测试标签的 int——然后是 B(如果我们去任何的话,还有同样挑剔的细节进一步)。

    由于两个字段描述符只有两个Get-able 项,您可能打算使用Get("test"),如Guarav Dhiman's answer

    但是,如果您这样做,则当您在字段 A 上时结果为 "testaaa",而当您在字段 B 上时结果为 "testbbb"。如果我们对 Guarav 的代码多加注解:

    for i := 0; i < tt.NumField(); i++ {
        field := tt.Field(i)
        newTTag := field.Tag.Get("test")
        fmt.Printf("newTTag = %#v\n", newTTag)
        tValue := tv.Field(i)
        newTfield := newTValue.Elem().FieldByName(newTTag)
        fmt.Printf("newTfield = %#v\n", newTfield)
        if newTfield.CanSet() {
            newTfield.Set(tValue)
        }
    }
    

    我们会看到这个输出:

    newTTag = "testaaa"
    newTfield = <invalid reflect.Value>
    newTTag = "testbbb"
    newTfield = <invalid reflect.Value>
    

    我们需要将每个标签名称中的test字符串设为newT类型中的字段

    type T struct {
        A int    `json:"aaa" test:"AA"`
        B string `json:"bbb" test:"BB"`
    }
    

    (Guarav 实际上已经这样做了,但没有提及。)现在程序产生了(大概)你想要的:

    &{123 hello}
    

    带有注释掉跟踪的完整程序是here

    【讨论】:

      猜你喜欢
      • 2013-07-06
      • 2021-09-12
      • 1970-01-01
      • 1970-01-01
      • 2014-06-27
      • 1970-01-01
      • 1970-01-01
      • 2022-06-24
      • 2013-04-06
      相关资源
      最近更新 更多