12
9

More than 3 years have passed since last update.

Go構造体の`json:hoge`の正体を探る

Posted at

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)
    }
}
result
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つの型を同一視する条件のようです(参考)。訳語が既にあったらコメント等で教えていただけるとありがたいです。 

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9