web-dev-qa-db-ja.com

Golang構造体のフィールドをループして、拡張可能な方法で値を取得および設定するにはどうすればよいですか?

Struct Personがあります。

type Person struct {
    Firstname string       
    Lastname  string       
    Years     uint8       
}

次に、この構造体の2つのインスタンス、PersonAとPersonBがあります。

PersonA := {"", "Obama", 6}
PersonB := {"President", "Carter", 8}

各フィールドにいくつかの条件(つまり、空でない)を指定して、PersonAからPersonBに値をコピーする関数を記述したいと思います。フィールド名をハードコーディングしてこれを行う方法を知っていますが、Person構造体を変更しても機能する関数が必要です。

Goリフレクションが役立つことは知っていますが、問題は値の取得と設定であり、SetIntのようなものを使用する場合はタイプを知る必要があります。しかし、これを行う「簡単な」方法はありますか?

** JavaScriptの類推** JavaScriptでは、(someObjectのプロパティに対して)ループするだけで済みます。

(for propt in personA) {
  if personA[propt] != "" {
    // do something
    personB[propt] = personA[propt]
  }
}

除外したオプション:

  1. マップの各構造体のフィールドを追跡し、次に reflect pkg でFieldByNameとSet *関数のコレクションの組み合わせを使用します。

  2. Personのフィールドを手動でループする(下記)。他の多くの構造体(学校、動物など)に対してこのタイプの「更新」を実行したいので

    if PersonA.Firstname != "" {
      PersonB.Firstname = PersonA.Firstname 
    }
    

    ...

    if PersonA.Years != "" {
      PersonB.Years = PersonA.Years 
    }
    

以下の質問では、途中までたどり着くことができますが、この「更新」機能を利用したいすべての構造体にはまだ拡張できません。

golangでは、reflectを使用して、構造体フィールドの値をどのように設定しますか?

**その他の役立つリンク** GoLang:名前による構造体プロパティへのアクセス

14
platwp

reflect.ValueOf()を使用して具象型に変換します。その後、 reflect.Value.SetString を使用して、必要な値を設定できます。

structValue := FooBar{Foo: "foo", Bar: 10}
fields := reflect.TypeOf(structValue)
values := reflect.ValueOf(structValue)

num := fields.NumField()

for i := 0; i < num; i++ {
    field := fields.Field(i)
    value := values.Field(i)
    fmt.Print("Type:", field.Type, ",", field.Name, "=", value, "\n")

    switch value.Kind() {
    case reflect.String:
        v := value.String()
        fmt.Print(v, "\n")
    case reflect.Int:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    case reflect.Int32:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    case reflect.Int64:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    default:
        assert.Fail(t, "Not support type of struct")
    }
}
10
jtianling

ここに解決策がありますf2.Set(reflect.Value(f))はここのキーです

package main

   import (
    "fmt"
    "reflect"
   )

   func main() {
    type T struct {
        A int
        B string
    }
    t := T{23, "skidoo"}
    t2:= T{}
    s := reflect.ValueOf(&t).Elem()
    s2 := reflect.ValueOf(&t2).Elem()
    typeOfT := s.Type()
    fmt.Println("t=",t)
    fmt.Println("t2=",t2)

    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        f2:= s2.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f.Type(), f.Interface())
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f2.Type(), f2.Interface())
        f2.Set(reflect.Value(f))
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f2.Type(), f2.Interface())

    }
    fmt.Println("t=",t)
    fmt.Println("t2=",t2)
}

Output:

t= {23 skidoo}
t2= {0 }
0: A int = 23
0: A int = 0
0: A int = 23
1: B string = skidoo
1: B string = 
1: B string = skidoo
t= {23 skidoo}
t2= {23 skidoo}

http://play.golang.org/p/UKFMBxfbZD

5
aarti

必要なのは反射だけです。これは https://godoc.org/github.com/getlantern/deepcopy で実装されている「ディープコピー」のセマンティクスに似ています(同一ではありません)。

あなたはそれをあなたのニーズに適応させることができるべきです、あるいは少なくともそこからいくつかのアイデアを取り入れることができなければなりません。

2
Evan

map[string]interface{}代わりに、はるかに高速になります(ただし、実際の構造体で適切なロジックを使用した場合ほど高速ではありません)。

package main

import "fmt"

type Object map[string]interface{}

var m = Object{
    "Firstname": "name",
    "Lastname":  "",
    "years":     uint8(10),
}

func main() {
    var cp = Object{}
    for k, v := range m {
        if s, ok := v.(string); ok && s != "" {
            cp[k] = s
        } else if ui, ok := v.(uint8); ok {
            cp[k] = ui
        }
    }
    fmt.Printf("%#v\n", cp)
}
1
OneOfOne

これがいくつ失敗するかはわかりません...

package main

import (
    "fmt"
    "encoding/json"
)

type Serializable interface {
    fromMap(map[string]interface{}) error
    toMap() (map[string]interface{}, error)
}

type Person struct {
    Firstname string       
    Lastname  string       
    Years     uint8       
}

func (p *Person) fromMap(m map[string]interface{}) error {
    b, err := json.Marshal(m)
    if err != nil {
        return err
    }

    if err := json.Unmarshal(b, p); err != nil {
        return err
    }
    return nil
}

func (p Person) toMap() (map[string]interface{}, error) {
    b, err := json.Marshal(p)
    if err != nil {
        return nil, err
    }
    m := map[string]interface{}{}
    if err := json.Unmarshal(b, &m); err != nil {
        return nil, err
    }
    return m, nil
}

func copy(p1 Serializable, p2 Serializable) error {

    m1, err := p1.toMap()
    if err != nil {
        return err
    }

    m2, err := p2.toMap()
    if err != nil {
        return err
    }

    for k := range m1 {
        m2[k] = m1[k]
    }

    if err := p2.fromMap(m2); err != nil {
        return err
    }
    return nil
}

func main() {
    p1 := Person{
        "Mary",
        "Jane",
        26,
    }

    p2 := Person {
        "Random",
        "Lady",
        26,
    }

    if err := copy(&p1, &p2); err != nil {
        fmt.Printf("ERR: %s\n", err.Error())
        return
    }

    fmt.Printf("%v\n", p2)

}
0
Kelsnare