Go構造体の`json:hoge`の正体を探る
(この記事は、2020年にQiitaに上げた記事の再投稿です。内容が古くなっている可能性がありますのでご了承ください。)
TL;DR
type Person struct {
Name string `json:"name"`
}
`json:"name"`
は構造体のタグreflect.TypeOf(Person).Field(0).Get("json")
で取り出せる!
はじめに
Go言語でjsonを扱うとき、構造体定義にjsonのフィールド名を書くと思います。
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}
おまじないと思って深く考えず使っていましたが、
- そもそも
`json:"name"`
って何? - ただのコメント?
- jsonのためだけの特別構文?
という疑問がわいたので調べてみました。
json:hoge は構造体のタグ
公式ドキュメントに早速答えがありました。ちゃんとふだんから読みなさい
The Go Programming Language Specification - The Go Programming Language
A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored.
フィールド宣言の後にはオプションで文字列リテラルのタグを付けることが可能で、これはフィールド宣言に対応する全てのフィールドの属性となります。空文字列のタグはタグ無しの場合と同等です。タグはリフレクションインターフェースにより可視化され、構造体の型の一意性1に関与しますが、それ以外の場合は無視されます。 (日本語は拙訳)
`json:"hoge"`
は構造体のタグであり、もともとGo言語にある構文を使っている(json用の特別なものではない)ことが分かります。
タグの取り出し方
ドキュメントの通り、タグは reflect
を使って取り出すことができます。
import (
"fmt"
"reflect"
)
type Person struct {
Name string `my name`
Age int `Don't look at this!`
}
func main() {
person := Person{Name: "Taro", Age: 23}
// リフレクションで型情報取得
personType := reflect.TypeOf(person)
for i := 0; i < personType.NumField(); i++ {
// タグを取得
tag := personType.Field(i).Tag
fmt.Println(tag)
}
}
my name
Don't look at this!
タグ内のキー
これだけでも便利ですが、タグを key:"value"
の形式で書くことで複数の属性を持たせることができます。キーに対応する値は Tag.Get(key)
で取り出せます。
encoding/json
はタグ中の json
キーを参照していたのですね。
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:"name" `
Age int `json:"age"`
// 他のキーも定義
Address string `json:"address" description:"where he lives" country:"Japan"`
}
func main() {
person := Person{Name: "Taro", Age: 23, Address: "Tokyo"}
personType := reflect.TypeOf(person)
fmt.Println(personType.Field(0).Tag.Get("json")) // name
fmt.Println(personType.Field(1).Tag.Get("json")) // age
fmt.Println(personType.Field(2).Tag.Get("json")) // address
// 他のキーも参照可能!
fmt.Println(personType.Field(2).Tag.Get("description")) // where he lives
fmt.Println(personType.Field(2).Tag.Get("country")) // Japan
}
実際に json.Marshal
を読んでみると、(呼び出しを追うのが大変ですが)Tag.Get("json")
でフィールド名を取り出す処理が確認できます。
タグを利用するライブラリ
json以外にもxml, yaml等色々なパッケージで利用されているみたいです。上手く活用していきたいですね!
Well known struct tags · golang/go Wiki · GitHub