Access struct property by name

GoGo Reflect

Go Problem Overview


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 ?

Go Solutions


Solution 1 - Go

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.

Solution 2 - Go

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

Solution 3 - Go

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

Solution 4 - Go

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNicolas BADIAView Question on Stackoverflow
Solution 1 - GoPaul HankinView Answer on Stackoverflow
Solution 2 - GoVonCView Answer on Stackoverflow
Solution 3 - GoCarsonView Answer on Stackoverflow
Solution 4 - GoDeepak SahView Answer on Stackoverflow