1

I am trying to get fields from a struct value using reflection.

package main

import (
    "fmt"
    "reflect"
)

type Vertex struct {
    X         string
    Y         string
    SubVertex SubVertex
}
type SubVertex struct {
    Z string
}

func get_field(v Vertex, field string) string {
    r := reflect.ValueOf(v)
    f := reflect.Indirect(r).FieldByName(field)
    return f.String()
}

func main() {
    v := Vertex{"a", "b", SubVertex{"c"}}

    fmt.Println(get_field(v, "X"))
    fmt.Println(get_field(v, "Y"))
    fmt.Println(get_field(v, "Z"))  // Invalid Value
}

I get Invalid Value in the third case, when I try to get the value of the Z field. If SubVertex were an anonymous field, this would work, but I need to use a named field.

How do I make this work?

Paul Hankin
  • 54,811
  • 11
  • 92
  • 118
irom
  • 3,316
  • 14
  • 54
  • 86
  • 2
    Works fine in the playground https://play.golang.org/p/fgnOdNRDA0 – RayfenWindspear Mar 28 '17 at 16:38
  • Not for me... https://play.golang.org/p/bMCKdzFe0i A B – irom Mar 28 '17 at 16:39
  • 1
    I edited the question -- it was confusing originally because working code was presented along with some slightly hard-to-understand changes described in prose that actually represent the problem. – Paul Hankin Mar 28 '17 at 16:41
  • PS: this appears to be a variant of code that I originally wrote in http://stackoverflow.com/questions/18930910/golang-access-struct-property-by-name/18931036#18931036 . Some of the things I said there apply here too -- especially about avoiding reflection. – Paul Hankin Mar 28 '17 at 16:43
  • 2
    If you don't embed SubVertex, you're using reflection to find the field Z in a struct that has no field Z (it has a field sub.Z, but not Z). If you need the sub field, you need to either look for the struct and then the field in that struct, or embed the struct. – Kaedys Mar 28 '17 at 16:44
  • I note also that the `reflect.Indirect` is redundant here -- in the original version of the code the Vertex was passed in via a pointer, but that's not the case here. – Paul Hankin Mar 28 '17 at 16:45
  • 1
    This works, but probably isn't what you want, but may give you an idea. You could maybe detect an invalid value and in that case, go deeper. https://play.golang.org/p/kgEDjYcovo – RayfenWindspear Mar 28 '17 at 16:47
  • yep, this gives me an idea, thnx. Using pointers does not change anything I think. search for "SubVertex.Z" in original version gives too. But now that get_subvertex_field makes it clearer for me – irom Mar 28 '17 at 16:51

1 Answers1

4

In this case, you have to use the reflect package in the same manner as you would accessing the values normally. So

v.X // a
v.Y // b
v.SubVertex.Z // c

becomes

r := reflect.ValueOf(v)
x := reflect.Indirect(r).FieldByName("X")
x.String() // a
...
z := reflect.Indirect(r).FieldByName("SubVertex").FieldByName("Z")
z.String() // c

Note that FieldByName() is called on a Value and returns a Value, so it works much the same as just accessing it regularly. Also note that as per the documentation:

Indirect returns the value that v points to. If v is a nil pointer, Indirect returns a zero Value. If v is not a pointer, Indirect returns v.

So the call to Indirect() would be a No-op, but would protect it from having a meltdown if you decided to give it a pointer in the future.

As for your function, this would work

func get_field(v Vertex, field string) string {
    r := reflect.ValueOf(v)
    if field == "Z" {
        f := reflect.Indirect(r).FieldByName("SubVertex").FieldByName(field)
        return f.String()
    }
    f := reflect.Indirect(r).FieldByName(field)

    return f.String()
}

https://play.golang.org/p/eZyTl8OSTZ

RayfenWindspear
  • 6,116
  • 1
  • 30
  • 42