2017-11-08 4 views
1

は私が持っているあなたが見ることができるように、変数のいくつかは、空のスライスであり、一部ではありませんhttps://play.golang.org/p/TlHCX29QZrフィールドをリフレクトを使用して構造体のスライスを変更するにはどうすればよいですか?

package main 

import (
    "fmt" 
    "reflect" 
) 

type A struct { 
    Name string 
    Age int 
} 

func change(a interface{}) { 
    aa := reflect.Indirect(reflect.ValueOf(a)) 

    for i := 0; i < aa.NumField(); i++ { 
     field := aa.Field(i) 

     switch field.Interface().(type) { 
     case string: 
      field.Set(reflect.ValueOf("fred")) 
     case int: 
      field.Set(reflect.ValueOf(54)) 
     default: 
      fmt.Println("unknown field") 
     } 
    } 
} 

func main() { 
    a := &A{"bob", 120} 
    b := []*A{} 
    c := []struct { 
     Alias string 
     Months int 
    }{} 

    d := []struct { 
     First string 
     Years int 
    }{ 
     {"james", 22}, 
     {"ricky", 32}, 
     {"bobby", 12}, 
     {"rachel", 82}, 
    } 


    change(a) 
    fmt.Println(a) // want &A{"fred", 54} 

    change(b) 
    fmt.Println(b) // want []*A{&A{"fred", 54}} 

    change(c) 
    fmt.Println(c) // want []struct{struct{"fred", 54}} 

    change(d) 
    fmt.Println(d) // want []struct{struct{"fred", 54}, struct{"fred", 54}, struct{"fred", 54}, struct{"fred", 54}} 
} 

次。空の場合、{"fred"、54}の1つの構造体を追加する必要があります。空ではないスライスについては、すべての値を{"fred"、54}に変更する必要があります。私は、フィールドが何であるかを事前に知っていません...文字列フィールドがある場合、値は "fred"でintフィールドの場合54.

"a "それ以外のものはすべて"パニックに反映されません:リフレクションの呼び出し:Value.NumField on Slice Value "。私はこれについてどこに行くべきか分からない。ありがとうございました!

+1

あなたは 'reflect.Kind's、structs、およびsliceの2つの異なるものを渡しています。スライスの場合は' NumField'を使うことができません。その内容を繰り返し処理しなければなりません。それらに 'NumField'を呼び出します。 – mkopriva

+0

また、論理的には2つの異なることをしたいと思っています。 bとcでは、スライスが空の場合は1つの値を「追加」し、もう一方の場合は値を変更したいとします。 – JimB

+0

@mkoprivaフィールド名を取得するために空の匿名構造体のスライスをどのように反復処理するのですか? –

答えて

1

コメントに記載されているように、スライスにNumFieldを使用することはできません。その方法はの種類がreflect.Structであるためです。あなたは空のスライスに追加したいのであれば

あなたが渡された1知っている必要があり、両方の種類を扱いたい場合。

if rv.Kind() == reflect.Struct { 
    changeStruct(rv) 
} 
if rv.Kind() == reflect.Slice { 
    changeSlice(rv) 
} 

は今、あなたのいずれかを指すポインタで渡す必要があり新しいスライスを返す必要があります。また

change(&b) 
change(&c) 

、あなたが最初のスライスのreflect.Typeを取得し、そのElemメソッドを使用して、スライスの要素の型を取得するために、あなたはあなたにそのタイプを知る最初の必要性を追加したいという単一の要素を初期化することができるようにしますスライスの要素の型を取得します。そのタイプでは、reflect.Newを使用して、そのタイプの新しい値を割り当ててスライスに追加することができます。あなたはreflect.Value.Lenreflect.Value.Indexメソッドを使用することができ、スライスにわたり、ループへ

var elem reflect.Value 

// rv is the slice 
typ := rv.Type().Elem() 
if typ.Kind() == reflect.Ptr { 
    elem = reflect.New(typ.Elem()) 
} 
if typ.Kind() == reflect.Struct { 
    elem = reflect.New(typ).Elem() 
} 

ln := rv.Len() 
for i := 0; i < ln; i++ { 
    changerv(rv.Index(i)) 
} 

コード:

func change(a interface{}) { 
    rv := reflect.ValueOf(a) 
    changerv(rv) 
} 

func changerv(rv reflect.Value) { 
    if rv.Kind() == reflect.Ptr { 
     rv = rv.Elem() 
    } 

    if rv.Kind() == reflect.Struct { 
     changeStruct(rv) 
    } 
    if rv.Kind() == reflect.Slice { 
     changeSlice(rv) 
    } 
} 

// assumes rv is a slice 
func changeSlice(rv reflect.Value) { 
    ln := rv.Len() 
    if ln == 0 && rv.CanAddr() { 
     var elem reflect.Value 

     typ := rv.Type().Elem() 
     if typ.Kind() == reflect.Ptr { 
      elem = reflect.New(typ.Elem()) 
     } 
     if typ.Kind() == reflect.Struct { 
      elem = reflect.New(typ).Elem() 
     } 

     rv.Set(reflect.Append(rv, elem)) 
    } 

    ln = rv.Len() 
    for i := 0; i < ln; i++ { 
     changerv(rv.Index(i)) 
    } 
} 

// assumes rv is a struct 
func changeStruct(rv reflect.Value) { 
    if !rv.CanAddr() { 
     return 
    } 
    for i := 0; i < rv.NumField(); i++ { 
     field := rv.Field(i) 

     switch field.Kind() { 
     case reflect.String: 
      field.SetString("fred") 
     case reflect.Int: 
      field.SetInt(54) 
     default: 
      fmt.Println("unknown field") 
     } 
    } 
} 

playground