reflectを使ったあれこれ

# 目的
`reflect`への理解力が欠如しているため、備忘録として残す。

## reflectを使って構造体のフィールドに値を入れる
### ゴール
フィールドの要素が不明な構造体に対して、特定の値を入れたい。

### go playground
[こちら](https://play.golang.org/p/wbrMl_xN3OX)

### 主な処理
#### 該当コード
再帰関数を利用した。

```go
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`。

```go
	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になるケースがある）。

```go
	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`以外の場合もあるので、もう少し単純化できるか調査中・・・）

```go
	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
[こちら](https://play.golang.org/p/pitrTKK2q_K)

### 主な処理
#### 該当コード
[こちら](#該当コード)の再帰関数をベースに改修。

```go
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`で構造体のタグ名が取得可能。

```go
	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`。
（他の型の対応方法も含め、もう少し、賢い書き方を調査中・・・）

```go
	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 `を指定できるように実現するケースも考慮したい。