Wie legen Sie mit Reflect den Wert eines Strukturfelds fest?

106

Es ist schwierig, mit Strukturfeldern mithilfe eines reflectPakets zu arbeiten. Insbesondere habe ich nicht herausgefunden, wie der Feldwert eingestellt werden soll.

Typ t struct {fi int; fs string}
var rt = t {123, "jblow"}
var i64 int64 = 456
  1. Name des Feldes i bekommen - das scheint zu funktionieren

    var field = reflect.TypeOf(r).Field(i).Name

  2. Abrufen des Werts von Feld i als a) Schnittstelle {}, b) int - dies scheint zu funktionieren

    var iface interface{} = reflect.ValueOf(r).Field(i).Interface()

    var i int = int(reflect.ValueOf(r).Field(i).Int())

  3. Einstellwert des Feldes i - try one - panic

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    Panic : Reflect.Value · SetInt verwendet einen Wert, der mit einem nicht exportierten Feld ermittelt wurde

    vorausgesetzt, es mochte nicht die Feldnamen "id" und "name", also umbenannt in "Id" und "Name"

    a) Ist diese Annahme richtig?

    b) falls korrekt, für nicht notwendig gehalten, da in derselben Datei / Paket

  4. Einstellwert von Feld i - versuchen Sie zwei (mit großgeschriebenen Feldnamen) - Panik

    reflect.ValueOf(r).Field(i).SetInt( 465 )

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    Panic : Reflect.Value · SetInt verwendet einen nicht adressierbaren Wert


Die folgenden Anweisungen von @peterSO sind gründlich und von hoher Qualität

Vier. das funktioniert:

reflect.ValueOf(&r).Elem().Field(i).SetInt( i64 )

er dokumentiert auch, dass die Feldnamen exportierbar sein müssen (beginnen mit Großbuchstaben)

cc jung
quelle
Das nächste Beispiel, das ich für jemanden finden konnte, der reflectDaten festlegte , war comment.gmane.org/gmane.comp.lang.go.general/35045 , aber selbst dort json.Unmarshalerledigte er die eigentliche Drecksarbeit
cc young
(Der obige Kommentar ist veraltet)
CC Young

Antworten:

155

Go ist als Open Source Code verfügbar . Ein guter Weg, um etwas über Reflexion zu lernen, ist zu sehen, wie die Go-Entwickler es verwenden. Zum Beispiel die Pakete Go fmt und json . Die Paketdokumentation enthält Links zu den Quellcodedateien unter der Überschrift Paketdateien.

Das Go json-Paket führt JSON von und zu Go-Strukturen ein und aus.


Hier ist ein schrittweises Beispiel, das den Wert eines structFeldes festlegt und dabei Fehler sorgfältig vermeidet.

Das Go- reflectPaket hat eine CanAddrFunktion.

func (v Value) CanAddr() bool

CanAddr gibt true zurück, wenn die Adresse des Werts mit Addr abgerufen werden kann. Solche Werte werden als adressierbar bezeichnet. Ein Wert ist adressierbar, wenn es sich um ein Element eines Slice, ein Element eines adressierbaren Arrays, ein Feld einer adressierbaren Struktur oder das Ergebnis der Dereferenzierung eines Zeigers handelt. Wenn CanAddr false zurückgibt, gerät der Aufruf von Addr in Panik.

Das Go- reflectPaket hat eine CanSetFunktion, die trueimpliziert, dass dies CanAddrauch der Fall ist true.

func (v Value) CanSet() bool

CanSet gibt true zurück, wenn der Wert von v geändert werden kann. Ein Wert kann nur geändert werden, wenn er adressierbar ist und nicht durch die Verwendung nicht exportierter Strukturfelder erhalten wurde. Wenn CanSet false zurückgibt, gerät der Aufruf von Set oder eines typspezifischen Setters (z. B. SetBool, SetInt64) in Panik.

Wir müssen sicherstellen, dass wir Setdas structFeld können. Beispielsweise,

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    // N at start
    fmt.Println(n.N)
    // pointer to struct - addressable
    ps := reflect.ValueOf(&n)
    // struct
    s := ps.Elem()
    if s.Kind() == reflect.Struct {
        // exported field
        f := s.FieldByName("N")
        if f.IsValid() {
            // A Value can be changed only if it is 
            // addressable and was not obtained by 
            // the use of unexported struct fields.
            if f.CanSet() {
                // change value of N
                if f.Kind() == reflect.Int {
                    x := int64(7)
                    if !f.OverflowInt(x) {
                        f.SetInt(x)
                    }
                }
            }
        }
    }
    // N at end
    fmt.Println(n.N)
}

Output:
42
7

Wenn wir sicher sein können, dass alle Fehlerprüfungen nicht erforderlich sind, vereinfacht sich das Beispiel zu:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    fmt.Println(n.N)
    reflect.ValueOf(&n).Elem().FieldByName("N").SetInt(7)
    fmt.Println(n.N)
}
peterSO
quelle
5
Gib auf! Irgendwo drin gibt es eine Antwort, aber vier Stunden Arbeit am json pkg haben es mir nicht gebracht. Was das Reflect-Paket betrifft, ist das Abrufen von Informationen ziemlich einfach, aber das Einstellen von Daten erfordert etwas schwarze Magie, für die ich gerne irgendwo ein einfaches Beispiel sehen würde!
CC junge
2
Hervorragend! Wenn Sie jemals in Thailand sind, lassen Sie sich bitte von mir ein oder zwei oder drei Bier gönnen! Vielen Dank
cc junge
5
Tolles praktisches Beispiel, dieser Artikel hat es für mich völlig entmystifiziert golang.org/doc/articles/laws_of_reflection.html
danmux
Tolles Beispiel. Hier ist ein Spielplatzbeispiel des gleichen Codes play.golang.org/p/RK8jR_9rPh
Sarath Sadasivan Pillai
Dies setzt kein Strukturfeld. Dies setzt ein Feld in einem Zeiger auf struct. Offensichtlich beantwortet dies nicht die gestellte Frage OP.
wvxvw
13

Das scheint zu funktionieren:

package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
    Number int
    Text string
}

func main() {
    foo := Foo{123, "Hello"}

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))

    reflect.ValueOf(&foo).Elem().Field(0).SetInt(321)

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))
}

Drucke:

123
321
Asgeir
quelle
Vielen Dank! Jetzt, wo ich die Notizen von peterSO gelesen habe, ist dies absolut sinnvoll. Ich habe foo verwendet, nicht & foo, konnte also nicht geändert werden und war mir nicht sicher, worum es bei Elem () ging.
CC junge