按名称访问struct属性

时间:2013-09-21 09:11:51

标签: go go-reflect

这是一个不起作用的简单go程序:

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

错误:

  

prog.go:18:无效操作:v [property](类型* Vertex的索引)

我想要的是使用其名称访问Vertex X属性。如果我v.X它有效,但v["X"]没有。

有人可以告诉我如何使这项工作?

4 个答案:

答案 0 :(得分:78)

大多数代码不应该需要这种动态查找。与直接访问相比,它是低效的(编译器知道Vertex结构中X字段的偏移,它可以将v.X编译为单个机器指令,而动态查找将需要某种哈希表实现或类似)。它还禁止静态类型:编译器无法检查您是否未尝试动态访问未知字段,并且无法知道结果类型应该是什么。

但是......语言提供了reflect模块,用于您需要的极少数时间。

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

这里没有错误检查,所以如果你要求一个不存在的字段,或者字段不是int类型,你会感到恐慌。查看the documentation for reflect了解详情。

答案 1 :(得分:10)

您现在拥有项目oleiade/reflections,它允许您获取/设置结构值或指针的字段 它使reflect package使用不那么棘手。

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

答案 2 :(得分:1)

使用 getAttr,您可以轻松获取和设置。

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
}

运行它 ? Go Playground

参考

答案 3 :(得分:0)

您可以封送该结构并将其取消封送回map[string]interface{}。但是,它将所有数值转换为float64,因此您必须手动将其转换为int

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