1

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?

Flimzy
  • 60,850
  • 13
  • 104
  • 147
Troskyvs
  • 5,653
  • 3
  • 23
  • 71

2 Answers2

3

It is as error call of reflect.flag.mustBeAssignable on zero Value says , newTValue.Elem().FieldByName(newTTag).CanSet() returns false in your your code and according to documentation

Set assigns x to the value v. It panics if CanSet returns false. As in Go, x's value must be assignable to v's type.

This is corrected code that takes fields from one Object and assigns value to another one.

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)
}
Gaurav Dhiman
  • 1,083
  • 4
  • 8
  • OK, this time it doesn't panic, but problem is still there: cannot set (CanSet() returns false). – Troskyvs Jan 19 '20 at 10:32
  • In your code it will return `false` because there is no tag named `newT` in any of `T`s field hence `newTTag` is empty string – Gaurav Dhiman Jan 19 '20 at 10:40
2

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.

torek
  • 330,127
  • 43
  • 437
  • 552