[]interface{} とか map[string]interface{} を TypeOf する

はじめに
--------------------

toml便利です。

- [BurntSushi/toml: TOML parser for Golang with reflection.](https://github.com/BurntSushi/toml)

でも `interface{}` 型で記述したパラメータの中に配列や連想配列が入ると、要素が `interface{}` 型になるのが不便です。

### 例

```go
type Config struct {
   ConfigA string                 `toml:"config_a"`
   ConfigB int64                  `toml:"config_b"`
   Options map[string]interface{} `toml:"options"`
}
```

```toml:config.toml
config_a = "a"
config_b = 0
[options]
  opt1 = "aaa"
  opt2 = ["aaa", "bbb"] # <- こいつが []interface{} 型になる
```

最近go製のフレームワークを作成しているのですが、バリデーションのために `reflect.TypeOf(config.options["opt2"])` で型を取ってみると、冒頭で述べた通り `[]interface{}` 型になりぐぬぬとなります。

つくったもの
--------------------
`interface{}` 型の中に配列・連想配列を突っ込んだときにも適切に方が取れる `TypeOf` 関数をつくりました。
注） 直近、配列と連想配列に対応できれば十分だったので、その2つにしか対応していません。あしからず。

```go
func typeOf(i interface{}) (reflect.Type, error) {
	switch v := i.(type) {
	case []interface{}:
		if len(v) == 0 {
			return nil, errors.New("no elements")
		}

		var elemType reflect.Type
		for _, el := range v {
			t, err := typeOf(el) // <- ここで再帰的に呼ぶ
			if err != nil {
				return nil, err
			}

			if elemType != nil && elemType != t {
				return nil, errors.New("multiple element types")
			} else {
				elemType = t
			}
		}

		return reflect.SliceOf(elemType), nil

	case map[string]interface{}:
		if len(v) == 0 {
			return nil, errors.New("no elements")
		}

		var elemType reflect.Type
		for _, el := range v {
			t, err := typeOf(el) // <- ここで再帰的に呼ぶ
			if err != nil {
				return nil, err
			}

			if elemType != nil && elemType != t {
				return nil, errors.New("multiple element types")
			} else {
				elemType = t
			}
		}

		return reflect.MapOf(reflect.TypeOf(""), elemType), nil

	default:
		return reflect.TypeOf(v), nil
	}
}
```

テストしてみる
--------------------

テスト用のtomlがこれ

```toml:test.toml
var0 = [0, 0]
var1 = ["string1", "string2"]
var2 = [["string1", "string2"], ["string1", "string2"]]

var3 = [] # <- これはエラーになる
var4 = [["string1", "string2"], [0, 0]] # <- これはエラーになる

[var5]
  aaa = "bbb"
  ccc = "ddd"

[var6]
  aaa = 0.0
  bbb = 0.0

[var7]
  [var7.aa]
    aaa = 0.0
  [var7.bb]
    bbb = 0.0

[var8]
  aaa = [0, 1, 2, 3]
  ccc = [2, 3, 4]

[var9] # <- これはエラーになる
  aaa = [0, 1, 2, 3]
  ccc = ["aaa", "bbb"]
```

main.go

```go
func main() {
	var config Config
	toml.DecodeFile("./test.toml", &config)

	fmt.Println(reflect.TypeOf(config.Var0))
	fmt.Println(reflect.TypeOf(config.Var1))
	fmt.Println(reflect.TypeOf(config.Var2))
	fmt.Println(reflect.TypeOf(config.Var3))
	fmt.Println(reflect.TypeOf(config.Var4))
	fmt.Println(reflect.TypeOf(config.Var5))
	fmt.Println(reflect.TypeOf(config.Var6))
	fmt.Println(reflect.TypeOf(config.Var7))
	fmt.Println(reflect.TypeOf(config.Var8))
	fmt.Println(reflect.TypeOf(config.Var9))

	fmt.Println(typeOf(config.Var0))
	fmt.Println(typeOf(config.Var1))
	fmt.Println(typeOf(config.Var2))
	fmt.Println(typeOf(config.Var3))
	fmt.Println(typeOf(config.Var4))
	fmt.Println(typeOf(config.Var5))
	fmt.Println(typeOf(config.Var6))
	fmt.Println(typeOf(config.Var7))
	fmt.Println(typeOf(config.Var8))
	fmt.Println(typeOf(config.Var9))
}
```
結果

```sh
[]interface {}
[]interface {}
[]interface {}
[]interface {}
[]interface {}
map[string]interface {}
map[string]interface {}
map[string]interface {}
map[string]interface {}
map[string]interface {}

[]int64 <nil>
[]string <nil>
[][]string <nil>
<nil> no elements # <- elementが無いので判別できない
<nil> multiple element types # <- 入れ子内のtypeが複数あるのでエラー
map[string]string <nil>
map[string]float64 <nil>
map[string]map[string]float64 <nil>
map[string][]int64 <nil>
<nil> multiple element types # <- 入れ子内のtypeが複数あるのでエラー
```