Search code examples
gosetfieldreflect

reflect runtime error: call of reflect.flag.mustBeAssignable on zero Value


I'm testing this code snippet on go playground, I aim to use reflect to get fields from one object, and then set value to another object

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

And it gives a very strange error:

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.

How to fix it?


Solution

  • First:

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

    Each step here iterates through the fields of your instance of type T. So the fields will be A—or rather, the field descriptor whose Name is A and which describes an int with its json and test tags—and then B (with the same picky details if we go any further).

    Since both field descriptors have only two Get-able items, you probably meant to use Get("test"), as in Guarav Dhiman's answer.

    If you do that, though, the result is "testaaa" when you are on field A and "testbbb" when you are on field B. If we annotate Guarav's code a bit more:

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

    we will see this output:

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

    What we need is to make the test string in each tag name the field in the newT type:

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

    (Guarav actually already did this but did not mention it.) Now the program produces what (presumably) you intended:

    &{123 hello}
    

    The complete program, with commented-out tracing, is here.