1

我在去操场上测试这个代码片段,我的目标是使用反射从一个对象获取字段,然后将值设置为另一个对象

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.

如何解决?

4

2 回答 2

3

正如错误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)
}
于 2020-01-19T10:10:48.617 回答
2

第一的:

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

这里的每一步都会遍历 type 实例的字段T。因此,这些字段将是A——或者更确切地说,是字段描述符,它的NameisA和它描述了一个带有 json 和 test 标记的 int ——然后B(如果我们再进一步,具有相同的挑剔细节)。

由于两个字段描述符只有两个Get-able 项目,因此您可能打算使用Get("test"),如Guarav Dhiman 的回答

但是,如果你这样做,结果是"testaaa"你在场上A"testbbb"场上的时候B。如果我们对 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}

带有注释掉跟踪的完整程序在这里

于 2020-01-19T10:37:04.287 回答