一度できればなんてことはないのですが、少しハマったので残しておきます。
この記事でやること
先に断りを入れると、下記の記事とStack Overflowを大いに参考にさせていただきました。
違いはHTTPリクエストであることと、文字コードがEUC-JPであることだけです。
前提条件
対象のAPIによって取得されるXML
<?xml version="1.0" encoding="EUC-JP"?> <data> <status>OK</status> <person> <familyname>山田</familyname> <givenname>太郎</givenname> <age>20</age> <birthday format="yyyy-MM-dd">1999-01-01</birthday> </person> <person> <familyname>佐藤</familyname> <givenname>治郎</givenname> <age>20</age> <birthday format="yyyy-MM-dd">1999-01-01</birthday> </person> </data>
構造体の定義
package person import "encoding/xml" type Data struct { XMLName xml.Name `xml:"data"` // ルート要素。なくてもOK Status string `xml:"status"` Person []Person `xml:"person"` } type Birthday struct { Format string `xml:"format,attr"` Date string `xml:",chardata"` } type Person struct { FamilyName string `xml:"familyname"` GivenName string `xml:"givenname"` Age int `xml:"age"` Birthday Birthday `xml:"birthday"` }
本処理
package person import ( "bytes" "context" "encoding/xml" "encoding/json" "fmt" "io" "net/http" "golang.org/x/text/encoding/japanese" "golang.org/x/text/transform" ) func main() { ctx := context.Background() response, err := getXML[Data](ctx) if err != nil { fmt.Println(err) } // jsonに変換して出力 res, err := json.Marshal(response) fmt.Println(string(res)) } func getXML[T any](ctx context.Context) (data T, err error) { url := "https://example.com/api/persons" // HTTP clientを作成 httpClient := &http.Client{ // 省略 } // HTTP Requestを作成 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return data, fmt.Errorf("create request failed: %s", err) } // HTTP Requestを実行 resp, err := httpClient.Do(req) if err != nil { return data, fmt.Errorf("http request failed: %s", err) } defer resp.Body.Close() // HTTP Response respBytes, err := io.ReadAll(resp.Body) if err != nil { return data, fmt.Errorf("read response failed: %s", err) } // XMLのデコード(ポイント1) decoder := xml.NewDecoder(bytes.NewReader(respBytes)) decoder.CharsetReader = makeCharsetReader // ポイント2 if err = decoder.Decode(&data); err != nil { return data, fmt.Errorf("XML parse failed: %s", err) } return data, nil } func makeCharsetReader(charset string, input io.Reader) (io.Reader, error) { if charset == "EUC-JP" { return transform.NewReader(input, japanese.EUCJP.NewDecoder()), nil } return input, nil }
解説
ポイント1
XMLパッケージにはUnmarshal関数がありますが、今回はHTTPリクエストで取得したデータをデコードしたいので、Decodeを使います。
Unmarshal関数を利用した場合、下記のエラーが発生します。
xml: encoding \"EUC-JP\" declared but Decoder.CharsetReader is nil"
ポイント2
Decodeを実行する際に、EUC-JPで読み出せるio.Readerを渡す必要があります。
ここでは記事を参考に、EUC-JPの場合はtransformパッケージを利用してラップしたものを返却し、それ以外はそのまま返却します。