Go標準パッケージを利用したJsonデータの読み込み&出力

## 概要

JSONの読み込みは「json.Unmarshal」「json.NewDecoder」の２種類、JSONの生成は「json.Marshal」「json.Marshalindent」「json.NewDecoder」の3種類を使った方法を紹介する。

## encoding/jsonパッケージ

JSONデータのエンコードとデコードをするためのパッケージ。
今回はこのパッケージを使用してJSONデータの解析を行う。

## JSONデータの読み込み

### json.Unmarshal

`json.Unmarshal()`はJSONを構造体に変換する。
第一引数にJSON形式のデータを、第二引数にポインタを指定することでJSON形式で受け取った値を指定したポインタに保存することができる。
```go
json.Unmarshal(JSON形式のデータ, ポインタ)
```

### JSONデータを解析してみる

今回は以下のようなJSONデータを用意する。ファイルの名前はtest.jsonとする。

```json
{
  "id" : 1,
  "content" : "Hello World!",
  "author" : {
    "id" : 2,
    "name" : "Goromaru"
  },
  "comments" : [
    {
      "id" : 1,
      "content" : "Rugby is great sport",
      "author" : "Matsushima"
    },
    {
      "id" : 2,
      "content" : "Try!!",
      "author" : "Fukuoka"
    }
  ]
}
```

以下がJSONデータを解析するコード。test.jsonは同じ階層にあるものとする。

```go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
)

type Post struct {
	Id       int       `json:"id"`
	Content  string    `json:"content"`
	Author   Author    `json:"author"`
	Comments []Comment `json:"comments"`
}

type Author struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
}

type Comment struct {
	Id      int    `json:"id"`
	Content string `json:"content"`
	Author  string `json:"author"`
}

func main() {
	jsonFile, err := os.Open("test.json")
	if err != nil {
		fmt.Println("JSONファイルを開けません", err)
		return
	}
	defer jsonFile.Close()
	jsonData, err := ioutil.ReadAll(jsonFile)
	if err != nil {
		fmt.Println("JSONデータを読み込めません", err)
		return
	}

	var post Post
	json.Unmarshal(jsonData, &post)

	fmt.Println(post)
	fmt.Println(post.Comments)
	fmt.Println(post.Comments[0].Content)

}
```
### ポイント

- まず`os.Open()`を使ってJSONファイルを開き、`ioutil.ReadAll()`で読み込む。
- 解析したJSONの情報を保存するための構造体を作成する必要がある。一番大きな枠組みとして構造体Postを定義している。
- test.jsonの`author`はキー・バリュー形式になっているので別途、構造体Authorを定義する。構造体PostのAuthorフィールドはAuthor型となる。
- test.jsonの`comments`はスライスになっているので、別途、構造体Commentを定義する。構造体PostのCommentsフィールドはComment型のデータを格納するスライスとする。
- `json.Unmarshal(jsonData, &post)`で読み込んだtest.jsonの情報を構造体postに格納している。
- `fmt.Println(post)`は以下のようになる。

```bash
{1 Hello World! {2 Goromaru} [{1 Rugby is great sport Matsushima} {2 Try!! Fukuoka}]}
```

- `fmt.Println(post.Comments)`は以下のようになる。

```bash
[{1 Rugby is great sport Matsushima} {2 Try!! Fukuoka}]
```

- `fmt.Println(post.Comments[0].Content)`は以下のようになる。

```bash
Rugby is great sport
```

### json.NewDecoderを使った読み込み方法

もう1つ、`json.NewDecoder()`を使う方法がある。
使い分けとしては、ストリームから情報を読み込んだ時は`json.NewDecoder()`を、それ以外は`json.Unmarshal()`を使うことになる。
`json.NewDecoder()`は例えば上記のtest.jsonに複数のJSONが含まれていても対応できるが、`json.Unmarshal()`は対応できない。

test.jsonを以下のように書き換えてみる。

```json
{
  "id" : 1,
  "content" : "Hello World!",
  "author" : {
    "id" : 2,
    "name" : "Goromaru"
  },
  "comments" : [
    {
      "id" : 1,
      "content" : "Rugby is great sport",
      "author" : "Matsushima"
    },
    {
      "id" : 2,
      "content" : "Try!!",
      "author" : "Fukuoka"
    }
  ]
}

{
  "id" : 2,
  "content" : "Hahahahaha",
  "author" : {
    "id" : 3,
    "name" : "Nakamura"
  },
  "comments" : [
    {
      "id" : 1,
      "content" : "Rugby is brilliant",
      "author" : "Yamanaka"
    },
    {
      "id" : 2,
      "content" : "Scrum!!",
      "author" : "Inagaki"
    }
  ]
}
```

`json.Unmarshal()`を使って解析しようとするとエラーが出る。

```bash
package ファイル名 is not in GOROOT (xxx/..../test.json)
```

`json.NewDecoder()`使用した場合以下のようになる。
※構造体の定義など重複する部分は省略。

```go

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
)

func main() {
	jsonFile, err := os.Open("test.json")
	if err != nil {
		fmt.Println("JSONファイルを開けません", err)
		return
	}
	defer jsonFile.Close()

	decoder := json.NewDecoder(jsonFile)
	for {
		var post Post
		err := decoder.Decode(&post)
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("JSONデータを読み込めません", err)
			return
		}
		fmt.Println(post)
	}
}
```

### ポイント

- `decoder := json.NewDecoder(jsonFile)`でファイルを読み出し、for文で`decoder.Decode(&post)`を実行することで構造体postにJSONデータを格納する。
- 出力結果は以下のようになる。

```bash
{1 Hello World! {2 Goromaru} [{1 Rugby is great sport Matsushima} {2 Try!! Fukuoka}]}
{2 Hahahahaha {3 Nakamura} [{1 Rugby is brilliant Yamanaka} {2 Scrum!! Inagaki}]}
```

## JSONデータの生成
### json.Marsharl

`json.Marsharl()`はJSONを生成する。`json.Marsharl()`はJSONを整形しないで１行で出力する。

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Post struct {
	Id       int       `json:"id"`
	Content  string    `json:"content"`
	Author   Author    `json:"author"`
	Comments []Comment `json:"comments"`
}

type Author struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
}

type Comment struct {
	Id      int    `json:"id"`
	Content string `json:"content"`
	Author  string `json:"author"`
}

func main() {
  post := Post{
    Id:      1,
    Content: "Hello World!",
    Author: Author{
      Id:   2,
      Name: "Goromaru",
    },
    Comments: []Comment{
      Comment{
        Id:      3,
        Content: "Rugby is great sport",
        Author:  "Matsushima",
      },
      Comment{
        Id:      4,
        Content: "Try!!",
        Author:  "Fukuoka",
      },
    },
  }

	output, err := json.Marshal(&post)
	if err != nil {
		fmt.Println("Error marshalling to JSON:", err)
		return
	}
	fmt.Println(string(output))
}
```

- `post := Post{…}`の形でJSONの内容を作成する。作成したいJSONの構造そのままに書けば良いのでそんなに難しくはない。
- `output, err := json.Marshal(&post)`のようにpostへのポインタを渡すと、JSONを生成してくれる。
- 以下のように出力される。

```bash
{"id":1,"content":"Hello World!","author":{"id":2,"name":"Goromaru"},"comments":[{"id":3,"content":"Rugby is great sport","author":"Matsushima"},{"id":4,"content":"Try!!","author":"Fukuoka"}]}
```

### json.MarshalIndent

`json.MarshalIndent()`はJSONを整形して出力する。以下のように使う。

```go
json.MarshalIndent(jsonを作成した構造体のポインタ, 各行の先頭につけるプレフィックス, インデント)
```

上記の`output, err := json.Marshal(&post)`を以下のように変えるだけ。

```go
output, err := json.MarshalIndent(&post, "", "\t\t")
```

### ポイント

- ファイルに書き出したり、コマンドラインに出力する時はこちらの方が良いだろう。
- 以下のようなきれいな出力になる。

```go
{
		"id": 1,
		"content": "Hello World!",
		"author": {
				"id": 2,
				"name": "Goromaru"
		},
		"comments": [
				{
						"id": 3,
						"content": "Rugby is great sport",
						"author": "Matsushima"
				},
				{
						"id": 4,
						"content": "Try!!",
						"author": "Fukuoka"
				}
		]
}
```

### json.NewDecoderを使った解析方法

こちらもまた、`json.NewDecoder()`を使ってJSONの生成ができる。
以下のように使う。※重複部分は省略している。

```go
err := json.NewEncoder(os.Stdout).Encode(post)
if err != nil {
	fmt.Println("Error encoding JSON to file:", err)
	return
}
```

- `json.NewEncoder()`は引数で指定された場所にJSONを出力する。この場合は標準出力している。
- `.Encode(post)`でPost構造体をエンコードしている。
- 以下のような出力になる。

```bash
{"id":1,"content":"Hello World!","author":{"id":2,"name":"Goromaru"},"comments":[{"id":3,"content":"Rugby is great sport","author":"Matsushima"},{"id":4,"content":"Try!!","author":"Fukuoka"}]}
```