【Go】PokeAPIで学ぶGo言語 ~画像データをJSONに含めレスポンスを返す~

# この記事の内容
* 外部APIから画像データを取得する
* 画像データをJSONに含めレスポンスを返す

# ポケモンの画像取得のエンドポイント
様々な世代、種類の画像がありますが今回は「from-default」という一般的なエンドポイントを使います。
エンドポイント
```
https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{ポケモンのID}.png
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301763/6dc97883-e91e-b231-f3a9-5c8ab8025340.png)

叩くとこんな感じでpngで画像が返されます。

これを前回作成した、計算後のステータス等と一緒にJSONで返したいわけです。

# 画像の取得
まずは普通にAPIを叩いて画像データを取得します。
```go
		//ポケモンの画像を取得
		resImg, err := http.Get("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/" + vars["id"] + ".png")
```
httpパッケージのGetメソッドでAPIを叩く。
データはbodyに含まれるのですが、今回は画像データなので取り出し方が少し異なります。
画像はバイナリデータのため、プログラムで扱いやすくするためにByte型に変換する必要があります。

取得したデータをByte型にするにはioパッケージのメソッドを使います。
引数に渡したデータを読み取ってByte型のスライスに格納してくれます。

```go
		pokeImg, err := io.ReadAll(resImg.Body)
```
しかしバイナリデータはJSONに含めることができません。

調べるとバイナリデータをテキスト形式で扱いたい場合はBase64という形式でエンコードするのが一般的な方法のようです。

https://developer.mozilla.org/ja/docs/Glossary/Base64

https://ja.wikipedia.org/wiki/Base64

GoでBase64でエンコードした文字列を生成したい場合はbase64パッケージの以下のメソッドを使います。
```Go
		pokeEncData := base64.StdEncoding.EncodeToString(pokeImg)
```
戻り値としてbase64形式でエンコードされた文字列が返されます。
構造体にフィールドを追加し、エンコードした文字列を含んで返せるようにします。

```go
	type PokeData struct {
		EncImg string `json:"img"` //追加
		Stats  []struct {
			BaseStat int `json:"base_stat"`
			CalStat  int `json:"cal_stat"`
			Stat     struct {
				Name string `json:"name"`
			} `json:"stat"`
		} `json:"stats"`
	}
```

# 挙動確認
```terminal
curl http://localhost:8080/pokemon/1?lv=50&ef=1&in=1
```

レスポンス
```json
{
    "img": "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgBAMAAAAQtmoLAAAAMFBMVEUAAAAQEBAYSkoxc3M5lJRSYili1bRzrDGD7sWk1UGsADG9/3PNzc3uIDn/amL///87EAzGAAAAAXRSTlMAQObYZgAAAZ1JREFUWMPtlL9Lw0AUx9NUce1JwMGlLUfbrdAGcVAkl0fI5iSloxiOrg4hiJuWcP+AlPwLASm4lWzB0dG9/0HAunbwpbjfq6PcZ/5+8n5weZZlMBgMhv/F4b75KaOHj5L4ZppMWuT8Y1Gssge6gPmiSLKYINjtukBRvJ0kK5IQPrXHyGhwWxTZyz1BUGOlUjlQOyHRlwhDpSCUKq2FBWFqX6k5hKmU0Wkt6Hs6xwKgdsIqW8Q0IVQpoJBlSawdojmYKRz6+aMHXrYgCPZnqubh7HJZgtdPiALUwmYnTLTCxTpUoObrfAtCUISrZYAzRMtqCz7040S7pbM8kLjSvHoHANHXCx35C/Da0D8NfiyDO8yPGLjQhY7+dV/nUt512DdgiS4X+tfK/bzGBYEdcWhrhYbjv1aVC54D2BRBQAOnBeaAK7CKR/hHPYagwz0cgiAcyAg3KiQIhyZYrsQoD6I9BPAYh1oQQDlMgYw8K0Ch4bouRWhGtSCjll2WJeXqDYe8ZfUC7IYx0qlsMGbZm83Xfqee+HGDwWAwGP7KD5Vqm0oTWykbAAAAAElFTkSuQmCC",
    "stats": [
        {
            "base_stat": 45,
            "cal_stat": 105,
            "stat": {
                "name": "hp"
            }
        },
        {
            "base_stat": 49,
            "cal_stat": 54,
            "stat": {
                "name": "attack"
            }
        },
        {
            "base_stat": 49,
            "cal_stat": 54,
            "stat": {
                "name": "defense"
            }
        },
        {
            "base_stat": 65,
            "cal_stat": 70,
            "stat": {
                "name": "special-attack"
            }
        },
        {
            "base_stat": 65,
            "cal_stat": 70,
            "stat": {
                "name": "special-defense"
            }
        },
        {
            "base_stat": 45,
            "cal_stat": 50,
            "stat": {
                "name": "speed"
            }
        }
    ]
}
```

imgフィールドにエンコードされた文字列がセットされるようになりました。

これを受け取ったブラウザ側で確認できるかみていきます。

確認用にindex.htmlを作成
```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Display Image from Binary Data</title>
    <style>
        body {
            text-align: center;
        }
        img {
            width: 300px;
            height: auto;
        }
    </style>
</head>
<body>
    <h1>バイナリデータから画像を表示</h1>
    <img id="imageDisplay" src="" alt="バイナリから生成された画像">
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            // 先ほどのレスポンスに含まれていたBase64エンコードされた値をセット
            const base64Data = '[エンコードされた文字列を入れる]';

            // img要素にエンコードされたデータをセット
            const imageElement = document.getElementById('imageDisplay');
            imageElement.src = `data:image/png;base64,${base64Data}`;
        });
    </script>
</body>
</html>
```

デコードして表示する必要があると思ったのですが、src属性にデータURIスキームなるものを指定するとブラウザ側で解釈して表示してくれるようです。
この部分です。
data:の後にメディアタイプとエンコード形式を,の後に対象データを渡すと解釈してくれます。
```javascript
imageElement.src = `data:image/png;base64,${base64Data}`;
```

開いてみると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301763/a2628189-95f8-1a75-394f-dcbec2e2beaa.png)

上手く表示できているようです。

コード
※今回追加した処理部分のみ抜粋
```Go
		//ポケモンの画像を取得
		resImg, err := http.Get("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/" + vars["id"] + ".png")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer resImg.Body.Close()

		pokeImg, err := io.ReadAll(resImg.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		pokeEncData := base64.StdEncoding.EncodeToString(pokeImg)

		var PokeData PokeData
		//レスポンスを構造体に変換
		if err := json.NewDecoder(res.Body).Decode(&PokeData); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		PokeData.EncImg = pokeEncData
```

# まとめ
恥ずかしながら画像データをJSONに含める実装をGoで初めて行ったので大変勉強になりました。
フレームワークに頼りであまり意識していなかったのですが、扱うデータの種類によって色々と考慮することがあるのですね。