GoでのJSON エンコードとデコードの基本

## はじめに

この記事では、Goを使用してJSON形式のデータする方法について解説します。標準ライブラリである`encoding/json`パッケージを使用して、Gonoデータ構造とJSON形式のデータを相互に変換する方法をサンプルコードを用いて説明します。


### 想定読者
- Go構造体 <-> JSON形式の変換方法を知りたい駆け出しGopher
- Goの構造体型への基本的な理解
- JSONが何かわかる

### 記事の狙い
- Go言語におけるJSONのエンコードとデコードの基本的な使い方を理解する


## 結論

- 構造体タグを使ってJSONオブジェクトのフィールドとGo構造体のフィールドをマッチングする
    - `json`というタグ名を使ってJSONフィールドを指定する
```go
// 構造体タグを使ったフィールド名の指定
type Person struct {
    Name   string `json:"name"`
    Age    int    `json:"age"`
    Email  string `json:"email"`
}
```
- JSONエンコード（Go構造体 -> JSON形式）
    - `Marshal`関数を使う
- JSONデコード（JSON形式 -> Go構造体）
    - `Unmarshal`関数を使う

## encoding/json

Goの標準ライブラリである`encoding/json`ライブラリはGoのデータ型とJSON間で相互に変換する機能を持っています。
- Goのデータ型からJSON形式への変換は **マーシャリング(marshaling)** と呼ばれる
- JSON形式からGoのデータ型への変換は **アンマーシャリング(unmarshaling)** と呼ばれる


### 構造体タグを使ったメタデータの付加

構造体タグを使うことで、JSON形式のデータとGoのデータ型の対応付けを行うことができます。構造体タグのルールは以下のようになっています。

- 構造体タグはバッククォート(`)で囲まれた文字列として定義される
- 構造体タグが行を跨ぐことはない
- \`json:"key"`の形式でキー名を指定する

```go
type Person struct {
    Name string `json:"name"`  // `Name`フィールドはJSONのキー名が`name`になる
}
```

- フィールドが空の場合に、出力に含めたくない場合はキー名のあとに`,omitempty`をつける。マーシャリング時(JSON -> Go構造体)の時だけ適用されるため、アンマーシャリング時には`omitempty`タグは無視され、JSONデータからすべてのフィールドが構造体フィールドに変換される


```go
type Person struct {
    Name    string `json:"name"`
    Age     int    `json:"age"`
    Address string `json:"address,omitempty"` // Addressが空の場合JSON出力から除外される
}
```

- キー名の前にハイフンをつけることで、マーシャリングの際にJSON出力から除外される。アンマーシャリング時にはハイフンは無視され、JSONデータに含まれているフィールドは対応する構造体フィールドに変換される（値はゼロ値が設定される）

```go
type Person struct {
    Name  string `json:"name"`
    Email string `json:"-"` // EmailフィールドはJSON出力に含まれない
}
```

### マーシャリングとアンマーシャリング

ここからは、具体的なコード例を交えてJSONのエンコードとデコードの方法を見ていきたいと思います。

#### マーシャリング(JSONエンコード)

Goの構造体型をJSON形式に変換するため`Marchal`関数を使います。

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name    string `json:"name"`
	Age     int    `json:"age"`
	Address string `json:"address,omitempty"`
}

func main() {
	// Personインスタンスを作成
	p := Person{
		Name: "John Doe",
		Age:  30,
	}

	// PersonインスタンスをJSONに変換
	b, err := json.Marshal(p)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(b))
}
```
上記のコードを実行すると、以下のようなJSON出力が得られます。`,omitempty`オプションを指定している`Address`フィールドは空であるため出力されていないことが分かります。

```bash
{
    "name": "John Doe",
    "age": 30
}
```


#### アンマーシャリング(JSONデコード)

JSON形式のデータをGoの構造体に変換するため`Unmarshal`関数を使用します。

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name    string `json:"name"`
	Age     int    `json:"-"`
	Address string `json:"address,omitempty"`
}

func main() {
	// JSONデータ
	jsonData := `{"name":"John","age":0}`

	// Personインスタンスを生成
	var p Person

	// JSONデータをPersonインスタンスに変換
	err := json.Unmarshal([]byte(jsonData), &p)
	if err != nil {
		fmt.Println(err)
		return

	}

	fmt.Printf("%+v\n", p)
}
```

上記のコードを実行すると、以下のような出力が得られます。`Age`フィールドにはハイフンを設定していますが、アンマーシャリングのため、対応するフィールドに変換されゼロ値が設定させています。`Address`フィールドについては、JSONに対応するフィールドが存在しませんが、出力には`Address`フィールドが設定されていることが分かります。

```bash
{Name:John Age:0 Address:}
```


## まとめ
この記事では、Go言語を使ってJSONデータと構造体の相互変換を行う方法について解説しました。標準ライブラリであるencoding/jsonパッケージを使用して、エンコード（マーシャリング）とデコード（アンマーシャリング）の基本的な使い方を説明しました。以下に主要なポイントをまとめます。

- マーシャリング (Go構造体 -> JSON形式):
    - `Marshal`関数を使用して、Go構造体をJSON形式に変換する
    - 構造体タグに`json:"fieldname,omitempty"`を付けることで、ゼロ値のフィールドをJSON出力から省略できる
    - `json:"-"`タグを付けたフィールドは、マーシャリングの際に常に出力から除外される
- アンマーシャリング (JSON形式 -> Go構造体):
    - `Unmarshal`関数を使用して、JSONデータをGo構造体に変換する
    - `omitempty`タグはアンマーシャリング時には無視され、JSONデータに含まれるすべてのフィールドが対応する構造体フィールドに変換される
    - `json:"-"`タグを付けたフィールドは、アンマーシャリング時には無視され、JSONデータに含まれるフィールドは対応する構造体フィールドに変換される（値はゼロ値が設定される）