LoginSignup
2
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-05-31

はじめに

toml便利です。

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

type Config struct {
   ConfigA string                 `toml:"config_a"`
   ConfigB int64                  `toml:"config_b"`
   Options map[string]interface{} `toml:"options"`
}
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つにしか対応していません。あしからず。

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がこれ

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

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))
}

結果

[]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が複数あるのでエラー
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1