GoでWeb APIにリクエストしXML形式のレスポンスをパースする

Web API にリクエストを送り、返却された XML 形式のレスポンスをパースする方法を解説します。

業務ではRubyとJavaScriptを書いており、Goは初心者です。
できるだけベストプラクティスに則れるよう書いていますが、より良い書き方などあれば教えて頂けますと幸いです。

## 環境

```
$ sw_vers
ProductName:    macOS
ProductVersion: 11.1
BuildVersion:   20C69

$ go version
go version go1.16.1 darwin/amd64
```

## 対象とする API のレスポンス

https://www.drk7.jp/weather/xml/13.xml
を対象にリクエストを送信します。

返却されるレスポンスのうち、必要な項目だけを抽出すると下記のようになります。詳細は上記URLにアクセスして、ご確認ください。

```sample.xml
<weatherforecast>
  <title>weather forecast xml</title>
  <link>http://www.drk7.jp/weather/xml/13.xml</link>
  <description>気象庁の天気予報情報を XML で配信。1日1回 AM 6:00 ごろ更新。</description>
  <pref id="東京都">
    <area id="伊豆諸島北部">
      <info date="2021/02/24">
        <weather>くもり</weather>
      </info>
      <info date="2021/02/25">
        <weather>くもり</weather>
      </info>
      <info date="2021/02/26">
        <weather>くもり時々雨</weather>
      </info>
      <info date="2021/02/27">
        <weather>くもり一時雨</weather>
      </info>
      <info date="2021/02/28">
        <weather>くもり</weather>
      </info>
      <info date="2021/03/01">
        <weather>くもり</weather>
      </info>
      <info date="2021/03/02">
        <weather>くもり一時雨</weather>
      </info>
    </area>
    <area id="伊豆諸島南部">
      <info date="2021/02/24">
        <weather>晴れのちくもり</weather>
      </info>
      <info date="2021/02/25">
        <weather>くもり</weather>
      </info>
      <info date="2021/02/26">
        <weather>くもり時々雨</weather>
      </info>
      <info date="2021/02/27">
        <weather>くもり一時雨</weather>
      </info>
      <info date="2021/02/28">
        <weather>くもり</weather>
      </info>
      <info date="2021/03/01">
        <weather>くもり</weather>
      </info>
      <info date="2021/03/02">
        <weather>くもり一時雨</weather>
      </info>
    </area>
    <area id="小笠原諸島">
      <info date="2021/02/24">
        <weather>くもり</weather>
      </info>
      <info date="2021/02/25">
        <weather>晴れ時々くもり</weather>
      </info>
      <info date="2021/02/26">
        <weather>くもり一時雨</weather>
      </info>
      <info date="2021/02/27">
        <weather>くもり一時雨</weather>
      </info>
      <info date="2021/02/28">
        <weather>くもり</weather>
      </info>
      <info date="2021/03/01">
        <weather>くもり時々晴れ</weather>
      </info>
      <info date="2021/03/02">
        <weather>くもり時々晴れ</weather>
      </info>
    </area>
    <area id="東京地方">
      <info date="2021/02/24">
        <weather>晴れ時々くもり</weather>
      </info>
      <info date="2021/02/25">
        <weather>くもり時々晴れ</weather>
      </info>
      <info date="2021/02/26">
        <weather>くもり一時雨</weather>
      </info>
      <info date="2021/02/27">
        <weather>くもり時々晴れ</weather>
      </info>
      <info date="2021/02/28">
        <weather>くもり</weather>
      </info>
      <info date="2021/03/01">
        <weather>くもり</weather>
      </info>
      <info date="2021/03/02">
        <weather>くもり一時雨</weather>
      </info>
    </area>
  </pref>
</weatherforecast>
```

## Web API へのリクエストとXML 形式のレスポンスのパースについて

重要なポイントをコメントで補足しています。

```main.go
package main

import (
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"net/http"
)

// レスポンスの形式に合わせて struct を定義する
type Data struct {
	Title       string `xml:"title"`       // タグの要素の取得
	Link        string `xml:"link"`        // タグの要素の取得
	Description string `xml:"description"` // タグの要素の取得
	Pref        Pref   `xml:"pref"`        // タグの要素の取得
}

type Pref struct {
	Areas []Area `xml:"area"` // タグの要素の取得
}

type Area struct {
	Id        string    `xml:"id,attr"` // タグの属性の取得
	DatesInfo DatesInfo `xml:"info"`    // タグの要素の取得
}

type DatesInfo []struct {
	Date    string `xml:"date,attr"` // タグの属性の取得
	Weather string `xml:"weather"`   // タグの要素の取得
}

func main() {
	resBody, err := httpGetBody("http://www.drk7.jp/weather/xml/13.xml")
	if err != nil {
		log.Fatal(err)
	}

	data := new(Data)
	if err := xml.Unmarshal(resBody, data); err != nil {
		log.Fatal("XML Unmarchal error:", err)
	}

	fmt.Printf("title: %s\n", data.Title)
	fmt.Printf("link: %s\n", data.Link)
	fmt.Printf("description: %s\n", data.Description)

	for _, area := range data.Pref.Areas {
		fmt.Printf("id: %s\n", area.Id)
		for _, info := range area.DatesInfo {
			fmt.Printf("date: %s ", info.Date)
			fmt.Printf("weather: %s\n", info.Weather)
		}
	}
}

func httpGetBody(url string) (body []byte, err error) {
	// HTTPリクエストを発行しレスポンスを取得する
	response, err := http.Get(url)
	if err != nil {
		// エラーをラップして返す
		err = fmt.Errorf("HTTP Get error: %v", err)
		return
	}

	// レスポンスボディを読み込む
	resBody, err := io.ReadAll(response.Body)
	if err != nil {
		// エラーをラップして返す
		err = fmt.Errorf("IO Read error: %v", err)
		return
	}

	// 読み込み終わったらレスポンスボディを閉じる
	defer response.Body.Close()

	return resBody, nil
}

```

実行結果は下記のとおりです。

```bash
$ go run main.go
title: weather forecast xml
link: http://www.drk7.jp/weather/xml/13.xml
description: 気象庁の天気予報情報を XML で配信。1日1回 AM 6:00 ごろ更新。
id: 伊豆諸島北部
date: 2021/02/24 weather: くもり
date: 2021/02/25 weather: くもり
date: 2021/02/26 weather: くもり時々雨
date: 2021/02/27 weather: くもり一時雨
date: 2021/02/28 weather: くもり
date: 2021/03/01 weather: くもり
date: 2021/03/02 weather: くもり一時雨
id: 伊豆諸島南部
date: 2021/02/24 weather: 晴れのちくもり
date: 2021/02/25 weather: くもり
date: 2021/02/26 weather: くもり時々雨
date: 2021/02/27 weather: くもり一時雨
date: 2021/02/28 weather: くもり
date: 2021/03/01 weather: くもり
date: 2021/03/02 weather: くもり一時雨
id: 小笠原諸島
date: 2021/02/24 weather: くもり
date: 2021/02/25 weather: 晴れ時々くもり
date: 2021/02/26 weather: くもり一時雨
date: 2021/02/27 weather: くもり一時雨
date: 2021/02/28 weather: くもり
date: 2021/03/01 weather: くもり時々晴れ
date: 2021/03/02 weather: くもり時々晴れ
id: 東京地方
date: 2021/02/24 weather: 晴れ時々くもり
date: 2021/02/25 weather: くもり時々晴れ
date: 2021/02/26 weather: くもり一時雨
date: 2021/02/27 weather: くもり時々晴れ
date: 2021/02/28 weather: くもり
date: 2021/03/01 weather: くもり
date: 2021/03/02 weather: くもり一時雨
```

## 補足) 構造体タグについて

構造体のあとにつく文字列を構造体タグと呼びます。
Go言語では構造体タグを使い、XMLと構造体をマッピングしています。

この構造体タグのポイントです。

- 要素を取得する場合
    - 要素名を指定する
    - 例) `xml:"title"`
- タグの属性を取得する場合
    - 属性名と`attr`を指定する
    - 例) `xml:"id,attr"`

## 補足) ネストした構造体について
Data,Pref,Area,DatesInfoの各構造体はネストして1つのStructを書くこともできます。
各構造体を再利用する場合は分けて書いたほうが良いですが、1つの用途だけならば、ネストして書くのもありですね。

```go
type Data struct {
	Title       string `xml:"title"`
	Link        string `xml:"link"`
	Description string `xml:"description"`
	Pref        struct {
		Areas []struct {
			Id        string `xml:"id,attr"` // タグの属性の取得
			DatesInfo []struct {
				Date    string `xml:"date,attr"`
				Weather string `xml:"weather"`
			} `xml:"info"`
		} `xml:"area"`
	} `xml:"pref"`
}
```


## 参考

タグの属性の取得について
[go - Golang XML attribute and value - Stack Overflow](https://stackoverflow.com/questions/23126133/golang-xml-attribute-and-value)