Доступ к свойству структуры по имени

Вот простая программа 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"] — нет.

Может кто-нибудь сказать мне, как заставить эту работу?


person Nicolas BADIA    schedule 21.09.2013    source источник


Ответы (4)


Большинству кодов такой динамический поиск не нужен. Это неэффективно по сравнению с прямым доступом (компилятор знает смещение поля X в структуре Vertex, он может скомпилировать 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. Дополнительные сведения см. в документации по Reflect.

person Paul Hankin    schedule 21.09.2013
comment
+1, а также см. Законы отражения, где дается введение в идею. - person Fred Foo; 21.09.2013
comment
Этот модуль отражения довольно сложен. Я пытался использовать его без успеха. Кажется, я забыл позвонить Ìndirect. Спасибо за рабочий пример и все объяснения. Очень ценю :-) - person Nicolas BADIA; 21.09.2013
comment
Спасибо за объяснение над кодом. Для меня это даже полезнее, чем сам код! - person Nebulosar; 28.12.2017

Теперь у вас есть проект oleiade/reflections, который позволяет вам получать/устанавливать поля на значение структуры или указатели.
Это упрощает использование пакета reflect.

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
person VonC    schedule 02.09.2014

С 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

Ссылка

person Carson    schedule 04.03.2021

Вы можете маршалировать структуру и демаршалировать ее обратно в 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)
}
person Deepak Sah    schedule 18.06.2020