【Go】mapや構造体を読みやすい形で標準出力させたい！！

# はじめに

こんにちは、ken です。お仕事ではよく Go を書いています。
突然ですが、コーディングをしているときに「この変数がどんな状態なのか確認したい」と思って、一時的に標準出力することってありますよね。
例えば WebAPI を呼び出すとき、レスポンスがどんな形で送られてくるのかを見たくなる機会は少なくないはずです。
しかし、ただ `fmt.Println` をするだけだとちょっと見にくいときがあります。今回はそれを見やすくする方法を先輩から教えていただいたので紹介したいと思います。

# fmt.Println だけだといかに見にくいか

まず、`fmt.Println` だけでの出力がいかに見にくいかを示します。
例として [PokeAPI](https://pokeapi.co/) という ポケモン の情報を呼び出す API を叩いてピカチュウの情報を取り出し、それを `fmt.Println` で標準出力します。

```go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

func PrintPikachu() {
	url := "https://pokeapi.co/api/v2/pokemon/pikachu/"

	resp, err := http.Get(url)
	if err != nil {
		fmt.Printf("failed to request to the API: %v\n", err)
		return
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("failed to read the response: %v\n", err)
		return
	}

	var pika Pokemon
	err = json.Unmarshal(body, &pika)
	if err != nil {
		fmt.Printf("failed to decode JSON: %v\n", err)
		return
	}

	fmt.Println(pika)
}

type Pokemon struct {
	ID        int           `json:"id"`
	Name      string        `json:"name"`
	Height    int           `json:"height"`
	Weight    int           `json:"weight"`
	Abilities []PokeAbility `json:"abilities"`
}

type PokeAbility struct {
	Ability Detail `json:"ability"`
}

type Detail struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

func main() {
	PrintPikachu()
}


```

このとき標準出力には何が出力されているのか見てみると
![スクリーンショット 2023-09-08 19.54.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/821422d1-e9f5-add6-7038-09cf51e70dbf.png)


```
{ID:25 Name:pikachu Height:4 Weight:60 Abilities:[{Ability:{Name:static URL:https://pokeapi.co/api/v2/ability/9/}} {Ability:{Name:lightning-rod URL:https://pokeapi.co/api/v2/ability/31/}}]}
```


うーん、読めないこともないですが、すべてが一行で出力されてるので少し見にくいですね。
特にネストされてるフィールドはどこまでがネストされてるところなのかが一目でわかるようにしたいです。

今回はピカチュウだけの情報なのでそんなにレスポンスの量としては大きくないですが、例えば次のようなユーザー ID を key にしてユーザー情報を value にもつ Map を標準出力するとどうでしょうか。

```go
func PrintUsers() {
	users := map[string]User{
		"ab06753b-29d2-ca0d-50d6-332609181108": {
			Name:          "鈴木太郎",
			Age:           15,
			Email:         "suzuki@example.com",
			SelfIntroduce: "こんにちは。私の名前は鈴木太郎です。趣味は釣りにいくことで、最近は10mほどのサメを釣り上げました。釣った後はそのサメと仲良くなって一緒に暮らしています。",
		},
		"62d57cdb-6034-38bf-a59e-427f3ee69cab": {
			Name:          "田中一郎",
			Age:           22,
			Email:         "tanaka@example.com",
			SelfIntroduce: "田中一郎です。趣味は月面を散歩することです。最近、月の裏側でエイリアンとティータイムを楽しむ機会がありました。彼らのお茶は地球のものとは一味違います。",
		},
		"ac28e78d-0c08-4bbf-7d74-94db6ea83e0e": {
			Name:          "佐藤花子",
			Age:           28,
			Email:         "sato.flower@example.com",
			SelfIntroduce: "佐藤花子と申します。夜空を見上げるのが大好きで、先日流れ星とレースをしました。私の勝ちです。",
		},
	}
	fmt.Println(users)
}

func main() {
	PrintUsers()
}

```

これを実行すると次のような結果を得ます。
![スクリーンショット 2023-09-08 19.53.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/6f0a91aa-d41c-6169-7ecb-238dc85fe7dc.png)


```
map[62d57cdb-6034-38bf-a59e-427f3ee69cab:{Name:田中一郎 Age:22 Email:tanaka@example.com SelfIntroduce:田中一郎です。趣味は月面を散歩することです。最近、月の裏側でエイリアンとティータイムを楽しむ機会がありました。彼らのお茶は地球のものとは一味違います。} ab06753b-29d2-ca0d-50d6-332609181108:{Name:鈴木太郎 Age:15 Email:suzuki@example.com SelfIntroduce:こんにちは。私の名前は鈴木太郎です。趣味は釣りにいくことで、最近は10mほどのサメを釣り上げました。釣った後はそのサメと仲良くなって一緒に暮らしています。} ac28e78d-0c08-4bbf-7d74-94db6ea83e0e:{Name:佐藤花子 Age:28 Email:sato.flower@example.com SelfIntroduce:佐藤花子と申します。夜空を見上げるのが大好きで、先日流れ星とレースをしました。私の勝ちです。}]
```

読みにくい…！！非常に読みにくいですね！
どこまでが key でどこまでが Value なのかすらわかりません。どうにかして読みやすくする方法はないのでしょうか？

# 解決策

結論から書くと、次の `prettyPrint` という関数を準備します。

```go
func prettyPrint(v any) {
	data, err := json.Marshal(v)
	if err != nil {
		fmt.Println(err)
		return
	}
	var buf bytes.Buffer
	err = json.Indent(&buf, data, "", "  ")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(buf.String())
}
```

この関数では、構造体を一旦 json 形式に変換して、Indent を加えてから出力するようにしています。
たったそれだけのことなのですが、これを `fmt.Println` の代わりに使うと標準出力が非常に見やすくなります。
この `prettyPrint` を用いてさっきのピカチュウのデータを出力してみると

```go
func PrintPikachu() {
	url := "https://pokeapi.co/api/v2/pokemon/pikachu/"
    // 省略

	// fmt.Println(pika)
	prettyPrint(pika) // ←追加
}

func main() {
	PrintPikachu()
}
```

```
{
  "id": 25,
  "name": "pikachu",
  "height": 4,
  "weight": 60,
  "abilities": [
    {
      "ability": {
        "name": "static",
        "url": "https://pokeapi.co/api/v2/ability/9/"
      }
    },
    {
      "ability": {
        "name": "lightning-rod",
        "url": "https://pokeapi.co/api/v2/ability/31/"
      }
    }
  ]
}
```

かなり見やすくなりましたね。

さっきの Users マップでも試してみます。

```go
func PrintUsers() {
	// 省略

	// fmt.Println(users)
	prettyPrint(users)
}

func main() {
	PrintUsers()
}
```

```
{
  "62d57cdb-6034-38bf-a59e-427f3ee69cab": {
    "Name": "田中一郎",
    "Age": 22,
    "Email": "tanaka@example.com",
    "SelfIntroduce": "田中一郎です。趣味は月面を散歩することです。最近、月の裏側でエイリアンとティータイムを楽しむ機会がありました。彼らのお茶は地球のものとは一味違います。"
  },
  "ab06753b-29d2-ca0d-50d6-332609181108": {
    "Name": "鈴木太郎",
    "Age": 15,
    "Email": "suzuki@example.com",
    "SelfIntroduce": "こんにちは。私の名前は鈴木太郎です。趣味は釣りにいくことで、最近は10mほどのサメを釣り上げました。釣った後はそのサメと仲良くなって一緒に暮らしています。"
  },
  "ac28e78d-0c08-4bbf-7d74-94db6ea83e0e": {
    "Name": "佐藤花子",
    "Age": 28,
    "Email": "sato.flower@example.com",
    "SelfIntroduce": "佐藤花子と申します。夜空を見上げるのが大好きで、先日流れ星とレースをしました。私の勝ちです。"
  }
}
```

かなり見やすくなりました！というかこの Users は変な人ばかり集まっていますね。こわい。

# おわりに

今回は、構造体や map の出力を見やすくする方法についてご紹介しました。
この記事が誰かを救うことにつながれば幸いです。ここまで読んでいただきありがとうございました！
間違いなどありましたらコメントにてご指摘ください。