Access struct property by name
Asked Answered
C

5

111

Here is a simple go program that is not working :

package main
import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getProperty(&v, "X"))
}

func getProperty(v *Vertex, property string) (string) {
    return v[property]
}

Error:

prog.go:18: invalid operation: v[property] (index of type *Vertex)

What I want is to access the Vertex X property using its name. If I do v.X it works, but v["X"] doesn't.

Can someone tell me how to make this work ?

Chantay answered 21/9, 2013 at 9:11 Comment(0)
P
157

Most code shouldn't need this sort of dynamic lookup. It's inefficient compared to direct access (the compiler knows the offset of the X field in a Vertex structure, it can compile v.X to a single machine instruction, whereas a dynamic lookup will need some sort of hash table implementation or similar). It's also inhibits static typing: the compiler has no way to check that you're not trying to access unknown fields dynamically, and it can't know what the resulting type should be.

But... the language provides a reflect module for the rare times you need this.

package main

import "fmt"
import "reflect"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getField(&v, "X"))
}

func getField(v *Vertex, field string) int {
    r := reflect.ValueOf(v)
    f := reflect.Indirect(r).FieldByName(field)
    return int(f.Int())
}

There's no error checking here, so you'll get a panic if you ask for a field that doesn't exist, or the field isn't of type int. Check the documentation for reflect for more details.

Physoclistous answered 21/9, 2013 at 9:27 Comment(3)
+1, and see also The Laws of Reflection which gives an introduction to the idea.Septimal
This reflect module is kind of tricky. I tried to use it without success. It seems that I was forgetting to call Ìndirect. Thanks for the working example and all the explanation. Really appreciate :-)Chantay
Thanks for the explanation above the code. For me, it's even more useful than the code itself!Passive
P
22

You now have the project oleiade/reflections which allows you to get/set fields on struct value or pointers.
It makes using the reflect package less tricky.

s := MyStruct {
    FirstField: "first value",
    SecondField: 2,
    ThirdField: "third value",
}

fieldsToExtract := []string{"FirstField", "ThirdField"}

for _, fieldName := range fieldsToExtract {
    value, err := reflections.GetField(s, fieldName)
    DoWhatEverWithThatValue(value)
}


// In order to be able to set the structure's values,
// a pointer to it has to be passed to it.
_ := reflections.SetField(&s, "FirstField", "new value")

// If you try to set a field's value using the wrong type,
// an error will be returned
err := reflection.SetField(&s, "FirstField", 123)  // err != nil
Periphery answered 2/9, 2014 at 5:26 Comment(0)
C
8

I want to offer a different approach that is not using reflection:

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getProperty(&v, "X"))
}

type Getter func(v *Vertex) int

var VertexAccess = map[string]Getter{
    "X": func(v *Vertex) int { return v.X },
    "Y": func(v *Vertex) int { return v.Y },
}

func getProperty(v *Vertex, property string) int {
    return VertexAccess[property](v)
}

https://go.dev/play/p/2E7LZBWx7yZ

This is an O(1) map lookup and a function call which should perform better than reflection. Obviously, you need some scaffolding code for every type you want to support. On the plus side, you can refactor your code easily; your function getProperty is a potential anti-pattern to https://martinfowler.com/bliki/TellDontAsk.html

Coth answered 12/1, 2023 at 22:14 Comment(0)
L
4

With getAttr, you can get and set easy.

package main

import (
    "fmt"
    "reflect"
)

func getAttr(obj interface{}, fieldName string) reflect.Value {
    pointToStruct := reflect.ValueOf(obj) // addressable
    curStruct := pointToStruct.Elem()
    if curStruct.Kind() != reflect.Struct {
        panic("not struct")
    }
    curField := curStruct.FieldByName(fieldName) // type: reflect.Value
    if !curField.IsValid() {
        panic("not found:" + fieldName)
    }
    return curField
}

func main() {
    type Point struct {
        X int
        y int  // Set prefix to lowercase if you want to protect it.
        Z string
    }

    p := Point{3, 5, "Z"}
    pX := getAttr(&p, "X")

    // Get test (int)
    fmt.Println(pX.Int()) // 3

    // Set test
    pX.SetInt(30)
    fmt.Println(p.X)  // 30

    // test string
    getAttr(&p, "Z").SetString("Z123")
    fmt.Println(p.Z)  // Z123

    py := getAttr(&p, "y")
    if py.CanSet() { // The necessary condition for CanSet to return true is that the attribute of the struct must have an uppercase prefix
        py.SetInt(50) // It will not execute here because CanSet return false.
    }
    fmt.Println(p.y) // 5
}

Run it on 👉 Go Playground

Reference

Lazy answered 4/3, 2021 at 7:16 Comment(0)
L
1

You can marshal the struct and unmarshal it back to map[string]interface{}. But, it would convert all the number values to float64 so you would have to convert it to int manually.

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getProperty(&v, "X"))
}

func getProperty(v *Vertex, property string) float64 {
    m, _ := json.Marshal(v)
    var x map[string]interface{}
    _ = json.Unmarshal(m, &x)
    return x[property].(float64)
}
Lag answered 18/6, 2020 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.