(この記事は、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") でフィールド名を取り出す処理が確認できます。

Marshal定義箇所

フィールド名取り出し処理

タグを利用するライブラリ

json以外にもxml, yaml等色々なパッケージで利用されているみたいです。上手く活用していきたいですね!

Well known struct tags · golang/go Wiki · GitHub


  1. type identityは2つの型を同一視する条件のようです (参考)。訳語が既にあったらコメント等で教えていただけるとありがたいです。 ↩︎