構造体一覧

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

Goの構造体には、タグ(Tag)という機能があります。
この機能は、構造体に実装したフィールドに対し、実行時に参照可能な任意のメタ情報を付与することができます。

タグで付与したメタ情報は、たとえば以下のような用途で活用できます。

  • jsonパッケージで構造体をJSONテキストに変換する際のキー名
  • DBテーブルや設定ファイルとのマッピング
  • 入力フォームのフィールド別のバリデーション条件

このように、WEB開発とも非常に親和性の高い機能です。
今回は、タグでメタ情報を付与する方法と、付与したメタ情報をプログラムから取得し参照する方法を解説したいと思います。

なお、構造体の基本的な知識については以下の記事をご参考ください。

【Go入門】構造体の基本(1) ポインタと構造体

【Go入門】構造体の基本(2) 構造体とメソッド

タグによるメタ情報の付与

タグでメタ情報を付与する方法は非常に簡単です。
構造体の各フィールド定義の後ろに、文字列リテラルを記述するだけです。

以下の例では、顧客情報を表すCustomer構造体を定義し、各フィールドの日本語名称をメタ情報として付与しています。

// 顧客情報構造体
type Customer struct {
    Id     int    `顧客ID`
    Name   string `顧客名`
    Age    int    `年齢`
    Gender string `性別`
}

タグの記述には、文字列リテラル(ダブルクォート“”で囲う)か、RAW文字列リテラル(バッククォートで囲う)のどちらかを使用できます。
ただし、タグ内のメタ情報内にダブルクォートを含めたい場合が想定されますので、バッククォートを使用したRAW文字列リテラルに統一しておくとよいでしょう。

一つのフィールドに複数のタグを付与する

一つのフィールドに複数のタグを付与したい場合、リテラル中にスペース区切りで各タグを記述します。
以下の例では、フィールドGenderに複数タグを定義しています。

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

さきほどの例では性別を文字列型にしており、”男性”や”女性”など文字列でデータを入力させる想定でした。
こちらの例では、区分値となる数値を入力させ、メタ情報を用いて対応する性別に変換する想定の実装になります。

なお、スペースでタグを区切る仕様のため、不要な箇所にスペースを入れると意図しない動作の原因となりますのでご注意ください。

メタ情報で付与したタグ情報の参照

タグでフィールドにメタ情報を付与する場合、当然ながら付与した情報を参照する用途を想定しています。
構造体に付与したメタ情報をプログラム内から参照するには、標準パッケージの一つであるreflectを使用します

reflectパッケージは、ランタイムリフレクション(反射型)を実装し、プログラムが任意の型のオブジェクトを操作するのを可能にします。

構造体のタグ情報を取得する

タグ情報を取得するには、reflectパッケージのTypeOf関数を使用します。

func TypeOf(i interface{}) Type

TypeOf関数は、引数で渡されたインターフェースの動的型を表す反射型を返します。
これは利用することで、フィールド名とともにタグ情報を取得することができます。

戻り値

それではサンプルを見てみましょう。

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行目で構造体のリフレクションrtCstを生成し、23行目から始まるループで各フィールドごとにフィールド名とタグ情報を取得し出力しています。

25行目のFieldは、構造体vのi番目のフィールドを返すメソッドです。
vの種類が構造体でない場合、またはiが範囲外の場合はパニックが発生します。

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

$ go run main.go
Id 顧客ID
Name 顧客名
Age 年齢
Gender description:性別 1:男性 2:女性 3:その他
構造体の値もあわせて取得したい場合

先の例では、タグ情報を参照するために構造体のフィールド情報を取得しました。
実際の開発では、タグ情報とあわせて各フィールドの値も取り扱いたいケースが多いと思います。

これには、いわゆる「構造体のフィールドを列挙するテクニック」が必要です。
これはより実践的な手法になりますので、次回の記事で詳細に解説したいと思います。

終わりに

タグを利用することにより、構造体のフィールドに、文字列を使って柔軟性の高いメタ情報を付与することが可能です。

ただし柔軟である一方で、タグはあくまで文字列にすぎないため、Goの特徴である厳格さはタグの内容には働きません。
仮に記述ミスがあったとしても、コンパイルエラーなどは発生しないため、気付かずに見落としてしまいがちです。

タグを利用する場合、記述ミスに充分注意し、きちんと期待通りの動作をするかテストを行うようにしてください。


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