Jak ustawić wartość pola struct za pomocą funkcji Reflect?

106

ciężko pracować z polami struct przy użyciu reflectpakietu. w szczególności nie wymyślili, jak ustawić wartość pola.

type t struct {fi int; fs string}
var rt = t {123, "jblow"}
var i64 int64 = 456
  1. uzyskanie nazwy pola i - wydaje się, że działa

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

  2. uzyskanie wartości pola i jako a) interface {}, b) int - wydaje się działać

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

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

  3. ustawienie wartości pola i - spróbuj jeden - panika

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

    panic : Reflect.Value · SetInt przy użyciu wartości uzyskanej przy użyciu niewyeksportowanego pola

    zakładając, że nie lubił nazw pól „id” i „name”, więc zmieniono ich nazwy na „Id” i „Name”

    a) czy to założenie jest prawidłowe?

    b) jeśli jest poprawny, uważany za niepotrzebny, ponieważ w tym samym pliku / pakiecie

  4. ustawienie wartości pola i - spróbuj dwa (z nazwami pól pisanymi wielkimi literami) - panika

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

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

    panic : Reflect.Value · SetInt przy użyciu nieadresowalnej wartości


Poniższe instrukcje autorstwa @peterSO są dokładne i wysokiej jakości

Cztery. to działa:

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

Dokumentuje również, że nazwy pól muszą być eksportowalne (zaczynać się od dużej litery)

cc młody
źródło
najbliższym przykładem, jaki mogłem znaleźć dla kogoś używającego reflectdo ustawiania danych, były komentarze.gmane.org/gmane.comp.lang.go.general/35045 , ale nawet tam on json.Unmarshalwykonywał rzeczywistą brudną robotę
cc młody
(powyższy komentarz jest nieaktualny)
cc młody

Odpowiedzi:

156

Go jest dostępny jako kod open source . Dobrym sposobem na poznanie refleksji jest zobaczenie, jak używają ich główni programiści Go. Na przykład pakiety Go fmt i json . Dokumentacja pakietu zawiera łącza do plików kodu źródłowego pod nagłówkiem Pliki pakietów.

Pakiet Go JSON organizuje i unmarshals JSON zi do struktur Go.


Oto przykład krok po kroku, który ustawia wartość structpola, uważnie unikając błędów.

reflectPakiet Go ma CanAddrfunkcję.

func (v Value) CanAddr() bool

CanAddr zwraca wartość true, jeśli adres wartości można uzyskać za pomocą Addr. Takie wartości nazywane są adresowalnymi. Wartość jest adresowalna, jeśli jest elementem wycinka, elementem tablicy adresowalnej, polem struktury adresowalnej lub wynikiem dereferencji wskaźnika. Jeśli CanAddr zwróci false, wywołanie Addr spowoduje panikę.

reflectPakiet Go ma CanSetfunkcję, która, jeśli true, sugeruje, że CanAddrrównież jest true.

func (v Value) CanSet() bool

CanSet zwraca true, jeśli wartość v można zmienić. Wartość można zmienić tylko wtedy, gdy jest adresowalna i nie została uzyskana przy użyciu niewyeksportowanych pól strukturalnych. Jeśli CanSet zwróci false, wywołanie Set lub dowolnego setera specyficznego dla typu (np. SetBool, SetInt64) spowoduje panikę.

Musimy upewnić się, że możemy pola. Na przykład,Setstruct

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

Jeśli możemy być pewni, że wszystkie kontrole błędów są niepotrzebne, przykład upraszcza się,

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
źródło
5
Poddać się! gdzieś tam jest odpowiedź, ale cztery godziny pracy nad pakietem json nie dały mi jej. Jeśli chodzi o pakiet Reflect, pobieranie informacji jest dość proste, ale ustawienie danych wymaga trochę czarnej magii, dla której chciałbym zobaczyć gdzieś prosty przykład!
cc młody
2
Wybitny! jeśli kiedykolwiek będziesz w Tajlandii, pozwól mi poczęstować Cię piwem, dwoma lub trzema! dziękuję bardzo
cc młody
5
Świetny praktyczny przykład, ten artykuł całkowicie mnie zdemistyfikował golang.org/doc/articles/laws_of_reflection.html
danmux
Niesamowite example.Here jest próbka plac zabaw dla dzieci z tego samego kodu play.golang.org/p/RK8jR_9rPh
Sarath Sadasivan Pillai
To nie ustawia pola struct. To ustawia pole we wskaźniku na struct. Oczywiście nie jest to odpowiedź na pytanie zadane przez OP.
wvxvw
14

To wydaje się działać:

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

Wydruki:

123
321
Asgeir
źródło
dzięki! teraz, kiedy przeczytałem notatki peterSO, ma to sens. Używałem foo, not & foo, więc nie można było tego zmienić i nie byłem pewien, o co chodzi w Elem ().
cc młody