目的
reflect
への理解力が欠如しているため、備忘録として残す。
reflectを使って構造体のフィールドに値を入れる
ゴール
フィールドの要素が不明な構造体に対して、特定の値を入れたい。
go playground
主な処理
該当コード
再帰関数を利用した。
func recursive(in reflect.Value, v int) {
rt := in.Kind()
if rt == reflect.Ptr {
vPtr := in
if in.IsNil() {
vPtr = reflect.New(in.Type().Elem())
}
recursive(vPtr.Elem(), v)
if in.CanSet() {
in.Set(vPtr)
}
return
}
if rt == reflect.Struct {
tValue := in.Type()
for i := 0; i < in.NumField(); i++ {
sf := tValue.Field(i)
if sf.PkgPath != "" && !sf.Anonymous {
continue
}
recursive(in.Field(i), v)
}
return
}
if rt == reflect.String {
strV := strconv.Itoa(v)
in.Set(reflect.ValueOf(strV))
return
}
if rt == reflect.Int {
in.Set(reflect.ValueOf(v))
return
}
}
処理概要
reflect.Value
の種別がポインタの場合
こちらの処理で、フィールドがポインタかどうか判別し、nil
の場合、reflct.Value
を生成。
nil
でなければ、再帰関数の第一引数をそのまま利用。
ポインタの参照先を再帰関数の第一引数に渡して、再度呼び出す。
呼び出し後、CanSet
がtrue
であれば、第一引数に、Value
をSet
。
if rt == reflect.Ptr {
vPtr := in
if in.IsNil() {
vPtr = reflect.New(in.Type().Elem())
}
recursive(vPtr.Elem(), v)
if in.CanSet() {
in.Set(vPtr)
}
return
}
reflect.Value
の種別が構造体の場合
構造体のフィールドを列挙し、それぞれのフィールドに対し、再帰関数の第一引数に渡して、再度呼び出す。
ただし、PkgPath
(パッケージ)のケースとAnonymous
(埋め込み型)のケースは除外(除外しないと永久loopになるケースがある)。
if rt == reflect.Struct {
tValue := in.Type()
for i := 0; i < in.NumField(); i++ {
sf := tValue.Field(i)
if sf.PkgPath != "" && !sf.Anonymous {
continue
}
recursive(in.Field(i), v)
}
return
}
reflect.Value
の種別がstring
、int
の場合
string
の場合、変換し、int
の場合、そのまま、reflect.ValueOf
でSet
。
(string
とint
以外の場合もあるので、もう少し単純化できるか調査中・・・)
if rt == reflect.String {
strV := strconv.Itoa(v)
in.Set(reflect.ValueOf(strV))
return
}
if rt == reflect.Int {
in.Set(reflect.ValueOf(v))
return
}
reflectを使って、map
から構造体のフィールドに値を入れ、不足データがあれば、その内容を出力
ゴール
map[string]string
から任意の構造体に変換し、構造体のフィールドの要素が不足であれば、その詳細を知りたい。
構造体のフィールドにはタグを指定し、フィールドがポインタ側であれば、オプショナル扱い。
また、bindParameters
の第一引数をinterface
型にすることで、任意の構造体に対応。
go playground
主な処理
該当コード
こちらの再帰関数をベースに改修。
func recursive(in reflect.Value, tag string, required bool, v map[string]string, res *map[string]interface{}) bool {
rk := in.Kind()
if rk == reflect.Ptr {
vPtr := in
if in.IsNil() {
vPtr = reflect.New(in.Type().Elem())
}
isSet := recursive(vPtr.Elem(), tag, false, v, res)
if in.CanSet() && isSet {
in.Set(vPtr)
}
return false
}
if rk == reflect.Struct {
tValue := in.Type()
isSet := false
for i := 0; i < in.NumField(); i++ {
sf := tValue.Field(i)
if sf.PkgPath != "" && !sf.Anonymous {
continue
}
tagName := ""
required = false
if uriTag := sf.Tag.Get(key); uriTag != "" {
tagName = uriTag
required = true
}
r := recursive(in.Field(i), tagName, required, v, res)
if r {
isSet = true
}
}
return isSet
}
ptrValue, errString := getValue(rk, required, tag, v)
if errString != "" {
e := *res
e[tag] = errString
res = &e
}
if ptrValue != nil {
value := *ptrValue
in.Set(value)
return true
}
return false
}
func getValue(kind reflect.Kind, required bool, tag string, m map[string]string) (*reflect.Value, string) {
var value string
var ok bool
if value, ok = m[tag]; !ok && required {
return nil, "parameter is not specified"
}
if value == "" && !required {
return nil, ""
}
if kind == reflect.String {
r := reflect.ValueOf(value)
return &r, ""
}
if kind == reflect.Int {
i, err := strconv.Atoi(value)
if err != nil {
return nil, "not numeric value"
}
r := reflect.ValueOf(i)
return &r, ""
}
return nil, ""
}
処理概要
構造体のタグ解析
reflect.StructField.sf.Tag.Get
で構造体のタグ名が取得可能。
if rk == reflect.Struct {
tValue := in.Type()
isSet := false
for i := 0; i < in.NumField(); i++ {
sf := tValue.Field(i)
if sf.PkgPath != "" && !sf.Anonymous {
continue
}
tagName := ""
required = false
if uriTag := sf.Tag.Get(key); uriTag != "" {
tagName = uriTag
required = true
}
r := recursive(in.Field(i), tagName, required, v, res)
if r {
isSet = true
}
}
return isSet
}
タグに相当する値をmap
から抽出
抽出できない場合、parameter is not specified
、string
に変換できない場合、not numeric value
をreturn
。
(他の型の対応方法も含め、もう少し、賢い書き方を調査中・・・)
if value, ok = m[tag]; !ok && required {
return nil, "parameter is not specified"
}
if value == "" && !required {
return nil, ""
}
if kind == reflect.String {
r := reflect.ValueOf(value)
return &r, ""
}
if kind == reflect.Int {
i, err := strconv.Atoi(value)
if err != nil {
return nil, "not numeric value"
}
r := reflect.ValueOf(i)
return &r, ""
}
その他
タグに-
、omitempty
を指定できるように実現するケースも考慮したい。