LoginSignup
1
3

More than 3 years have passed since last update.

Go でリフレクションを使って struct をバリデーションする

Posted at

Go 言語で、テストに必要な Variables がちゃんとセットされているか確認したいという要望があった。その Variables もいろんなところに分散していたら面倒なので、一か所で書きたかった。つまりこんなイメージ。

type Foo struct {
    Bar string `env:"TF_VAR_hello"`
    Baz string `env:"TF_VAR_world"`
}

こんな感じで構造体を書いておくと、自動で、TF_VAR_helloTF_VAR_world の環境変数を読み込んで、それをそれぞれのフィールドにセットしてほしい、そしてすべてのフィールドがちゃんとなんかしらの値がセットされていてほしい。

このような要件の時はリフレクションだけど、Goではやったことなかったので調べて試してみた。

フィールドの読み込み

定義されたすべてのフィールドの読み込みは次のコードで可能になる。ちなみに、s は、ポインターでも実体でも良い。

func validate(s interface{}) {
    val := reflect.ValueOf(s).Elem()

    for i := 0; i < val.NumField(); i++ {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        tag := typeField.Tag

        fmt.Printf("Field Name: %v, \t Field Value: %s,\t Tag value: %s\n", typeField.Name, valueField, tag)
    }
}

Mainから実行してみる。

func main() {
    foo := &Foo{
        Bar: "abc",
        Baz: "def",
    }
    validate(foo)
}

実行結果

Field Name: Bar,         Field Value: abc,       Tag value: env:"TF_VAR_hello"
Field Name: Baz,         Field Value: def,       Tag value: env:"TF_VAR_world"

フィールドへの値のセット

フィールドへの値のセットは次のように行う。interface{}なので、型チェックをしてから、SetString() などで型を明確にしてとると良い。

    for i := 0; i < val.NumField(); i++ {
        if val.Field(i).Kind() == reflect.String {
            val.Field(i).SetString("overriden")
        }
    }

更新した後は、ついでに、Tagの値をとるようにしましょう。とても簡単。


    for i := 0; i < val.NumField(); i++ {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        tag := typeField.Tag

        fmt.Printf("Field Name: %v, \t Field Value: %s,\t Tag value: %s\n", typeField.Name, valueField, tag.Get("env"))
    }

実行結果

$ go run *.go
Field Name: Bar,         Field Value: abc,       Tag value: env:"TF_VAR_hello"
Field Name: Baz,         Field Value: def,       Tag value: env:"TF_VAR_world"
Field Name: Bar,         Field Value: overriden,         Tag value: TF_VAR_hello
Field Name: Baz,         Field Value: overriden,         Tag value: TF_VAR_world

ちなみに、interface{} を元の型にキャストしたいときはこんな感じ。TypeValidationが必要です。ちなみに、DeserializeVariablesStruct は自分で書いた関数で、Tagを元に環境変数から値をセットして、
Structを戻す関数です。

    result := DeserializeVariablesStruct(&Foo{})
    foo := result.(*Foo)

思ったよりずっと簡単やった。もっとAPIを掘りたいところやけど、今日はここまで。

全ソースコード

package main

import (
    "fmt"
    "reflect"
)

// Foo is a testing struct
type Foo struct {
    Bar string `env:"TF_VAR_hello"`
    Baz string `env:"TF_VAR_world"`
}

func main() {
    foo := &Foo{
        Bar: "abc",
        Baz: "def",
    }
    validate(foo)
}

func validate(s interface{}) {
    val := reflect.ValueOf(s).Elem()

    for i := 0; i < val.NumField(); i++ {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        tag := typeField.Tag

        fmt.Printf("Field Name: %v, \t Field Value: %s,\t Tag value: %s\n", typeField.Name, valueField, tag)
    }

    for i := 0; i < val.NumField(); i++ {
        if val.Field(i).Kind() == reflect.String {
            val.Field(i).SetString("overriden")
        }
    }

    for i := 0; i < val.NumField(); i++ {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        tag := typeField.Tag

        fmt.Printf("Field Name: %v, \t Field Value: %s,\t Tag value: %s\n", typeField.Name, valueField, tag.Get("env"))
    }

}

1
3
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
1
3