按名称访问结构体属性

下面是一个不起作用的简单应对程序:

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

错误:

Go: 18: 无效操作: v [ property ](类型为 * Vertex 的索引)

我想要的是访问顶点 X 属性使用它的名称。如果我做 v.X,它工作,但 v["X"]不。

有人能告诉我怎么做吗?

120305 次浏览

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.

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

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

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