Struct タグについて
たとえば struct で構造化されている情報を特定のファイルやデータベースに出力したり,逆にファイルやデータベースの情報を struct に流し込みたい場合に struct の各フィールドに目印になる情報があると便利である。 この目印として機能するのが struct タグである1。
struct タグは以下のように記述する。
type Server struct {
Host string `elem:"host"`
IPAddress string `elem:"ip_address"`
Port int `elem:"port"`
Note string `elem:"note"`
}
このタグ情報を取得するには reflect
パッケージを使う。
たとえばこんな感じ。
package main
import (
"fmt"
"reflect"
)
type Server struct {
Host string `elem:"host"`
IPAddress string `elem:"ip_address"`
Port int `elem:"port"`
Note string `elem:"note"`
}
func main() {
s := Server{}
t := reflect.TypeOf(s)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Name=%s , tag(elem)=%s\n", field.Name, field.Tag.Get("elem"))
}
}
これを実行するとこうなる。
Name=Host , tag(elem)=host
Name=IPAddress , tag(elem)=ip_address
Name=Port , tag(elem)=port
Name=Note , tag(elem)=note
実際には reflect
を直接使う局面は少なく,既にあるパッケージを利用することが多い。
たとえば struct による構造化データを JSON 形式に出力する encoding/json
パッケージがある。
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
Host string `json:"host"`
IPAddress string `json:"ip_address"`
Port int `json:"port"`
Note string `json:"note"`
}
func main() {
s := Server{Host: "localhost", IPAddress: "127.0.0.1", Port: 8080, Note: "Web Application"}
j, err := json.MarshalIndent(s, "", " ")
if err != nil {
return
}
fmt.Println(string(j))
}
これを実行するとこうなる。
{
"host": "localhost",
"ip_address": "127.0.0.1",
"port": 8080,
"note": "Web Application"
}
Server
の内容が JSON 形式で出力されているのが分かるだろう。
JSON の要素名がタグで指定した名前になっていることを確認してほしい。
反対もやってみよう。
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
Host string `json:"host"`
IPAddress string `json:"ip_address"`
Port int `json:"port"`
Note string `json:"note"`
}
func main() {
svr := []byte(`{
"host": "localhost",
"ip_address": "127.0.0.1",
"port": 8080,
"note": "Web Application"
}`)
var s Server
if err := json.Unmarshal(svr, &s); err != nil {
return
}
fmt.Println(s)
}
実行結果はこうなる。
{localhost 127.0.0.1 8080 Web Application}
きれいに struct に値が入っているのが分かると思う。
ちなみにタグの書式は key:"value"
だが,間違って記述しても単に無視されるだけでコンパイル時も実行時もエラーにならないので注意が必要である。
なおタグ書式の文法ミスについては,静的検査ツールの vet でチェックできる。
タグは複数列挙することができる。 たとえばサンプルの構造体を TOML にも対応させたいなら
type Server struct {
Host string `json:"host" toml:"host"`
IPAddress string `json:"ip_address" toml:"ip_address"`
Port int `json:"port" toml:"port"`
Note string `json:"note" toml:"note"`
}
などとする(デリミタは空白文字)。
じゃあ,先ほどと同じようにして TOML で出力してみる。
TOML を扱うには github.com/BurntSushi/toml
パッケージを使うとよい。
package main
import (
"bytes"
"fmt"
"github.com/BurntSushi/toml"
)
type Server struct {
Host string `json:"host" toml:"host"`
IPAddress string `json:"ip_address" toml:"ip_address"`
Port int `json:"port" toml:"port"`
Note string `json:"note" toml:"note,omitempty"`
}
func main() {
s := Server{Host: "localhost", IPAddress: "127.0.0.1", Port: 8080, Note: ""}
t := new(bytes.Buffer)
if err := toml.NewEncoder(t).Encode(s); err != nil {
return
}
fmt.Println(t.String())
}
実行結果は以下の通り。
host = "localhost"
ip_address = "127.0.0.1"
port = 8080
omitempty
オプションはフィールドが空(nil
または空文字列)の場合に出力を省略できる2。
このオプションは encoding/json
パッケージでも使える。
ついでに反対もやってみよう。
package main
import (
"fmt"
"github.com/BurntSushi/toml"
)
type Server struct {
Host string `json:"host" toml:"host"`
IPAddress string `json:"ip_address" toml:"ip_address"`
Port int `json:"port" toml:"port"`
Note string `json:"note" toml:"note,omitempty"`
}
func main() {
svr := `
host = "localhost"
ip_address = "127.0.0.1"
port = 8080
note = "Web Application"
`
var s Server
if _, err := toml.Decode(svr, &s); err != nil {
return
}
fmt.Println(s)
}
結果は以下の通り。
{localhost 127.0.0.1 8080 Web Application}
このように struct で正規化できる情報であれば,タグ機能を使うことでアプリケーション外部とのやり取りがだいぶ楽になる。
ブックマーク
- Go で struct のタグ情報を取得する - hiyosi’s blog
- struct にアノテーションつけてたら go vet . すべき - Qiita
- Goのjson.Marshal/Unmarshalの仕様を整理してみる - I Will Survive
- BurntSushi/tomlを使ってハマったこと - Qiita
- GoでJSONの一部分を利用者が定義した構造体に読み込める便利な手法を見つけた - Qiita
- Go 言語 1つの構造体に複数の validation を適応する - Qiita : struct タグに validation 情報を埋め込んで利用する
- Goのencoding/xmlを使いこなす - Qiita
- Go言語でJSON内の整数は10進数6桁しか表現できない - Qiita
- GolangでEnumをフィールドに持つstructをいい感じにjsonエンコード / デコードする - 一から勉強させてください( ̄ω ̄;)
-
「アノテーション(annotation)」と呼ぶ人もいる。たぶん Java の annotation 機能を意識しているんだろう。 ↩︎
-
数値の場合は
omitzero
オプションを付けると 0 のときに出力を省略できる。ただしBurntSushi/toml
パッケージではDecode()
がうまく動かないらしい。実はomitempty
オプションもDecode()
時の挙動が怪しいんだよなぁ。 TOML パーサの別実装としては naoina/toml というのもある。これは最新の TOML 仕様に追随しているようだがomitzero
オプションには対応していない。 ↩︎