Go 言語の Struct Tag と JSON エンコーディング

# はじめに

この記事は [MYJLab Advent Calendar 2023](https://qiita.com/advent-calendar/2023/myjlab) の 12/24 の記事です。     

こんにちは! 3年の [@ea_gitro](https://qiita.com/ea_gitro) です!

昨日は[いーじー](https://qiita.com/easy_yuki_)先輩の「[初心者さん向けのUnity](https://qiita.com/easy_yuki_/items/c4423fc0e259e70fa680)」講座でした!   
僕は昔Unityをやろうと思って、そのあまりの重さにパソコンくんがダウンしてしまったので断念したことがあります。先輩の記事を機にまたはじめてみようかな!


さて、はやいものでもう 12/24 です...  
今年ももう少しだというのにやり残したことが山ほどあります...   

残された時間は大切に使っていきたいですね!

## この記事について

皆さんが「**～をやってみた!**」「**～入門!**」という前向きな記事を出す中、今日僕が出す記事は「**Go言語の Struct Tag と JSON エンコーディング**」というとてもニッチなものになってしまいました...

自分が書き溜めていたものを出そうと思っていたので、問題解決的な記事になってしまって申し訳ないです。  
この記事が誰かの役に立つことを願ってます!




# Struct Tag とは?
`struct tag` とは各フィールドの末尾につける**メタ情報**のこと。`tag string` ともいう。     

`` `key:"value"` `` という RAW 文字列で表されることがおおい。    
(この形でなくてもいいが、多くのパッケージでは`key`, `value` の組で使われる。)  

また `key` と `value` の間には空白を開けてはならない。    
逆に空白を開けることで、複数の struct tag を設定することができる。
```go
type 構造体型名 struct{
	フィールド名 型 `key1:"value1"`
	フィールド名 型 `key2:"value2" key3:"value3"`
}
```

`reflect` パッケージを使えば、 `tag string` を取得できる。   
(コードは [https://pkg.go.dev/reflect#example-StructTag](https://pkg.go.dev/reflect#example-StructTag) より。少し改変)
```go
package main

import (
	"fmt"
	"reflect"
)

func main() {
	type S struct {
		F string `species:"gopher" color:"blue"`
	}

	s := S{}
	st := reflect.TypeOf(s) // `main.S`
	field := st.Field(0) // F のフィールドを取得(`Field()`はインデクスで取得。)
	// field, _ := st.FieldByName("F") //　`FieldByName()` はフィールド名で取得。存在するか否かも返す
	
    fmt.Println(    // `フィールド変数.Tag.Get("キー名")` でそのキーの値を返す
		field.Tag.Get("species"),		// gopher
		field.Tag.Get("color"), 		// blue
	)

}
```


# Strcut Tag の活用(JSONエンコーディング)

この `struct tag` が実際に使われるのが `json` パッケージ (`"encoding/json"`)などである(パッケージ名に注意。 `encoding` の中の `json` である)

`json` パッケージでは構造体を JSON に変換することができるが、その際に `struct tag` を用いることがある。   



- フィールド定義の後ろで `` `json:"JSONのキー名"`  ``とすることで、構造体のフィールド名ではなく **設定したキー名を JSON のキー名にする** ことができる
	- 何も設定しなければ構造体のフィールド名がそのまま JSON のキー名になる  



- ただし **小文字で始まるフィールド名は encode/decode されない** ので、フィールドを **大文字にする必要** がある 
	- これは変数と同様に、構造体のフィールド名も大文字でないとパッケージの外から参照できないためである 
		- [The Go Spec - Exported identifiers](https://go.dev/ref/spec#Exported_identifiers)
			>  ### Exported identifiers
			> An identifier may be exported to permit access to it from another package. An identifier is exported if both:  
			> 	1. the first character of the identifier's name is a Unicode uppercase letter (Unicode character category Lu); and  
			> 	1. the identifier is declared in the package block or it is a field name or method name.  
			> 
			> All other identifiers are not exported.
            > [DeepL翻訳]
            > 識別子は、他のパッケージからのアクセスを許可するためにエクスポートする ことができる。識別子がエクスポートされるのは
            > 識別子名の**最初の文字がUnicodeの大文字**（Unicode文字カテゴリLu）である。
            > 識別子がパッケージブロックで宣言されているか、フィールド名またはメソッド名である。
            > それ以外の識別子はエクスポートされません。
		- [The Go Blog](https://go.dev/blog/json)
			> The json package only accesses the exported fields of struct types (those that begin with an uppercase letter). Therefore only the exported fields of a struct will be present in the JSON output.
            > [DeepL翻訳]
            > jsonパッケージは、構造体タイプのエクスポートされたフィールド（大文字で始まるフィールド）のみにアクセスします。したがって、JSON出力には、構造体のエクスポートされたフィールドのみが存在します。



- `encode` パッケージは バイトコード <=> テキスト の変換を行う



- `json.Marshal(構造体)` とすることで構造体を JSON に変換できるが、返値が `type []byte` なのでここでは `string()` を使って表示している
	- 逆に JSON => 構造体 とするには `json.Unmarshal(JSONバイト配列, 構造体ポインタ)` とする
		- この場合もキー名が小文字の場合フィールドの内容が消えるので注意


```go
package main

import (
	"encoding/json"
	"fmt"
)

type Human struct {
	Name    string   `json:"name"`      // "name"
	Age     int                         // 何も設定していないので "Age" になる
	Hobbies []string `json:"hobbies"`   // "hobbies"
	tf      bool     `json: "tf"`       // そもそもフィールド名が小文字なので encode されない
}

func main() {
	taro := Human{
		Name:    "Taro",
		Age:     6,
		Hobbies: []string{"baseball", "game", "books"},
		tf:      true,
	}
	j, err := json.Marshal(taro)    // 構造体 => JSON
	if err == nil {
		fmt.Println(
			string(j),      // {"name":"Taro","Age":6,"hobbies":["baseball","game","books"]}
		)
	} else {
		fmt.Println(err)
	}

	var taro2 Human

	err = json.Unmarshal(j, &taro2) // JSON => 構造体

	fmt.Printf("%#v\n", taro2) // main.Human{Name:"Taro", Age:6, Hobbies:[]string{"baseball", "game", "books"}, tf:false}
	// 構造体に値が代入されている。この場合大文字小文字は無視される("age" => "Age") 値が入ったように見えるのはゼロ値。(false)

	fmt.Println(taro2.tf)  // false  <= boolean のゼロ値(実際には値は入ってない。元からの値)
	fmt.Println(taro2.Age) // 6		 <= "age"(JSON) の値が "Age"(構造体) に入った
}
```



# 参考文献
1. The Go Spec.   
	[https://go.dev/ref/spec](https://go.dev/ref/spec), (accessed: 2023-11-24)


1. @itkr. "Goの構造体にメタ情報を付与するタグの基本". Qiita.   
	[https://qiita.com/itkr/items/9b4e8d8c6d574137443c](https://qiita.com/itkr/items/9b4e8d8c6d574137443c), (accessed: 2023-11-24)

https://qiita.com/itkr/items/9b4e8d8c6d574137443c

2. Go Packages. "reflect - StructTag".    
	[https://pkg.go.dev/reflect#StructTag](https://pkg.go.dev/reflect#StructTag), (accessed: 2023-11-24)

3. Go Packages. "json - Marshal".    
	[https://pkg.go.dev/encoding/json#Marshal](https://pkg.go.dev/encoding/json#Marshal), (accessed: 2023-11-24)



4. まくまく. "Golang の構造体にタグ情報を追加する (struct tags)". まくまく Golang ノート.   
	[https://maku77.github.io/p/hxhzfbs/](https://maku77.github.io/p/hxhzfbs/), (accessed: 2023-11-24)

5. @Yarimizu14. "【Golang】structのField名で気をつけるところ". Qiita.   
	[https://qiita.com/Yarimizu14/items/e93097c4f4cfd5468259](https://qiita.com/Yarimizu14/items/e93097c4f4cfd5468259), (accessed: 2023-11-24)

https://qiita.com/Yarimizu14/items/e93097c4f4cfd5468259

6. Andrew Gerrand. "JSON and Go". The Go Blog   
	[https://go.dev/blog/json](https://go.dev/blog/json), (accessed: 2023-11-24)