構造体一覧

【Go入門】構造体のフィールド定義、値、メタ情報を取得して列挙する

前回の記事で、構造体のタグ(Tag)機能によりメタ情報を付与する方法を解説しました。

【Go入門】構造体のタグ ~ メタ情報の付与と取得

この際、タグで付与したメタ情報を構造体から取得するために、reflectパッケージによって対象の構造体の反射型を取得する方法を解説しました。
今回はメタ情報だけでなく、構造体のフィールド名や型、代入されている値などを取得して列挙するコードを紹介します。
取得したフィールドすべてに対して処理を行うための実践的なテクニックですので、ぜひ身につけてください。

前回のコードのおさらい

前回の記事で、構造体のタグ情報を取得するために以下のようなコードを記述しました。

package main

import (
    "fmt"
    "reflect"
)

// 顧客情報構造体
type Customer struct {
    Id     int    `顧客ID`
    Name   string `顧客名`
    Age    int    `年齢`
    Gender int    `description:性別 1:男性 2:女性 3:その他`
}

// 構造体のタグ情報を取得する関数
func getStructTag(cst Customer) {

    // reflect.TypeOfでCustomer構造体のリフレクションを取得
    rtCst := reflect.TypeOf(cst)

    // 構造体の全フィールドを取得するループ
    for i := 0; i < rtCst.NumField(); i++ {
        // フィールド情報を取得
        f := rtCst.Field(i)
        // 取得したフィールド名とタグ情報を出力
        fmt.Println(f.Name, f.Tag)
    }
}

func main() {

    // 顧客情報構造体を生成
    cst := Customer{Id: 1, Name: "山田hoge郎", Age: 24, Gender: 1}
    // タグ情報取得関数の実行
    getStructTag(cst)
}

このコードのポイントは、20行目のreflect.TypeOf関数で構造体の動的な反射型を取得していることです。
これにより、メタ情報を含む構造体の定義を取り扱うことができます。

しかし、これだけでは構造体に代入された実値を扱うことはできません。

ValueOf関数で構造体に格納された値を取得する

構造体のデータを取得する為には、reflect.ValueOf関数を使用します。

func ValueOf(i interface{}) Value

ValueOf関数は、引数で渡されたインターフェースに格納されている値で初期化された新しいValueを返します。
引数がnilの場合はゼロ値を返します。

こうして取得した新しいValueには、reflectパッケージに定義される様々なメソッドを使用することできます。
これらのメソッドを使用することにより、構造体の情報をすべて取得し列挙することができます。

フィールド情報と値の列挙

サンプルコード

それでは、先にサンプルコードと実行結果を確認してから、コード内で使用している個々のメソッドについて解説したいと思います。

package main

import (
    "fmt"
    "reflect"
    "strconv"
)

// 顧客情報構造体
type Customer struct {
    Id     int    `顧客ID`
    Name   string `顧客名`
    Age    int    `年齢`
    Gender int    `description:性別 1:男性 2:女性 3:その他`
}

// 構造体のタグ情報を取得する関数
func getStructTag(cst Customer) {

    // reflect.TypeOfでCustomer構造体のリフレクションを取得
    rtCst := reflect.TypeOf(cst)
    // reflect.TypeOfでCustomer構造体の値を取得
    rvCst := reflect.ValueOf(cst)

    // 構造体の全フィールドを取得するループ
    for i := 0; i < rtCst.NumField(); i++ {
        // フィールド情報を取得
        f := rtCst.Field(i)
        // FieldByNameメソッドでフィールド名に対応する値を取得
        v := rvCst.FieldByName(f.Name).Interface()

        // 取得した値の型をチェックし数値であれば文字列に変換
        value := ""
        kind := ""
        if _, ok := v.(int); ok {
            value = strconv.Itoa(v.(int))
            kind = "int"
        } else {
            value = v.(string)
            kind = "string"
        }

        // フィールド名、値、タグ情報を出力
        fmt.Printf("[フィールド名] %s [型] %s [値] %s [メタ情報] %s\n", f.Name, kind, value, f.Tag)

    }
}

func main() {

    // 顧客情報構造体を生成
    cst := Customer{Id: 1, Name: "山田hoge郎", Age: 24, Gender: 1}
    getStructTag(cst)

}

このサンプルの実行結果は以下のようになります。

$ go run main.go
[フィールド名] Id [型] int [値] 1 [メタ情報] 顧客ID
[フィールド名] Name [型] string [値] 山田hoge郎 [メタ情報] 顧客名
[フィールド名] Age [型] int [値] 24 [メタ情報] 年齢
[フィールド名] Gender [型] int [値] 1 [メタ情報] description:性別 1:男性 2:女性 3:その他

フィールド定義、構造体にセットした具象値、メタ情報がそれぞれ取得できていることが確認できますね。

NumFieldメソッド ― 構造体のフィールド数を取得

それでは個別のメソッドを解説していきます。
まず、25行目をみてみましょう。

    // 構造体の全フィールドを取得するループ
    for i := 0; i < rtCst.NumField(); i++ {

ループの条件節にNumFieldというメソッドを使用していますね。
NumFieldがぶら下がっているrtCstは、TypeOf関数で取得した構造体の反射型です。

func (v Value) NumField() int

NumFieldは、構造体vのフィールド数を返します。
「カウンタ変数iがNumFieldの値よりも小さい場合」という条件指定で、構造体のフィールド数だけループ処理を行うことができます。

Fieldメソッド ― 構造体のフィールド数を取得

次に、27行目をみてみましょう。

        // フィールド情報を取得
        f := rtCst.Field(i)

カウンタ変数を引数として、Fieldというメソッドを使用しています。

func (v Value) Field(i int) Value

Fieldは、構造体vのi番目のフィールドを返し、フィールド情報を表す構造体を返します。
この構造体は、NameやTagなどのフィールドを持っています。
このため、構造体の定義情報を容易に取り扱うことができます。

FieldByNameメソッド ― フィールド名から具象値を取得

最後に、29行目をみてみましょう。

        // FieldByNameメソッドでフィールド名に対応する値を取得
        v := rvCst.FieldByName(f.Name).Interface()

構造体の名前を引数にして、FieldByNameというメソッドをしています。
FieldByNameがぶら下がっているrvCstは、ValueOf関数で取得した構造体の具象値です。

ffunc (v Value) FieldByName(name string) Value

FieldByNameは、指定された名前の構造体フィールドの値を返します。
指定された名前のフィールドが見つからなかった場合はゼロ値を返します。

取得した値はインターフェース型ですので、文字列や数値として扱うことができません。
このため、以下のように型の判定を行い、用途にあわせて型キャストを行う必要があります。

        // 取得した値の型をチェックし数値であれば文字列に変換
        value := ""
        kind := ""
        if _, ok := v.(int); ok {
            value = strconv.Itoa(v.(int))
            kind = "int"
        } else {
            value = v.(string)
            kind = "string"
        }

終わりに

構造体に対しループで各フィールドを処理するこのようなロジックは、データベースや入力フォームなどを扱う実践的な開発でも頻出するテクニックです。

reflectパッケージ自体はやや理解しにくいものではありますが、TypeOfValueOfさえ扱えれば、それに紐づくメソッドは直感的に使用できるかと思いますので、最初はあまり難しく考えずに使い方を覚えておけばよいでしょう。


スポンサーリンク
スポンサーリンク
スポンサーリンク