外部のJSON APIからデータを取得し、必要な形に整形する方法を学びましょう。
1. はじめに 📝
多くのWebサービスやアプリケーションは、他のシステムとデータを交換するためにJSON APIを提供しています。 例えば、天気情報、ニュース記事、商品情報などを取得する際に利用されます。
このセクションでは、Goの標準パッケージを使ってJSON APIにリクエストを送り、取得したデータをGoのプログラムで扱いやすい形(構造体など)に変換(整形)する方法を学びます。 これにより、外部サービスと連携するアプリケーションを開発できるようになります。
2. net/http パッケージでAPIを叩く (GETリクエスト) 🌐
まず、外部APIからデータを取得するための基本的な方法として、HTTPのGETリクエストを送信します。Goではnet/http
パッケージのGet
関数が便利です。
package main
import (
"fmt"
"io"
"log"
"net/http"
)
func main() {
// 例として、JSONPlaceholderというダミーAPIのエンドポイントを使用します
apiURL := "https://jsonplaceholder.typicode.com/todos/1"
// HTTP GETリクエストを送信
resp, err := http.Get(apiURL)
if err != nil {
log.Fatalf("HTTP GETリクエストに失敗しました: %v", err)
}
// 関数終了時にレスポンスボディを必ず閉じる
defer resp.Body.Close()
// ステータスコードをチェック (200 OK以外はエラーとして扱う例)
if resp.StatusCode != http.StatusOK {
log.Fatalf("エラーレスポンスを受け取りました: %s", resp.Status)
}
// レスポンスボディを読み込む
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("レスポンスボディの読み込みに失敗しました: %v", err)
}
// 取得したJSONデータを文字列として出力
fmt.Println("取得したJSONデータ:")
fmt.Println(string(body))
}
このコードは、指定されたURLにGETリクエストを送り、レスポンスボディの内容を読み取ってコンソールに出力します。
エラーハンドリングも重要です。リクエスト自体のエラー、ステータスコードのエラー、ボディ読み込みエラーをチェックしています。
defer resp.Body.Close()
はリソースリークを防ぐために不可欠です。
3. encoding/json パッケージでJSONをGoの構造体にデコード ✨
APIから取得したJSONデータ(バイト列)をGoのプログラムで扱いやすくするために、Goの構造体(struct)に変換します。この処理をデコードまたはアンマーシャリングと呼びます。encoding/json
パッケージのUnmarshal
関数を使います。
まず、受け取るJSONの構造に対応するGoの構造体を定義します。
// 受け取るJSONデータに対応する構造体
type Todo struct {
UserID int `json:"userId"` // JSONのキー名とGoのフィールド名をマッピング
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
構造体のフィールド名の後ろにある `json:"..."`
は構造体タグと呼ばれ、JSONデータ内のキー名とGoの構造体のフィールド名を対応付けます。これにより、JSONのキー名が小文字始まりでも、Goの公開フィールド(大文字始まり)に正しくマッピングできます。
次に、取得したJSONデータ(body
変数)をこのTodo
構造体にデコードします。
package main
import (
"encoding/json" // jsonパッケージをインポート
"fmt"
"io"
"log"
"net/http"
)
// JSONデータに対応する構造体
type Todo struct {
UserID int `json:"userId"`
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
func main() {
apiURL := "https://jsonplaceholder.typicode.com/todos/1"
resp, err := http.Get(apiURL)
if err != nil {
log.Fatalf("HTTP GETリクエストに失敗しました: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("エラーレスポンスを受け取りました: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("レスポンスボディの読み込みに失敗しました: %v", err)
}
// JSONデータをTodo構造体にデコード(アンマーシャリング)
var todo Todo
err = json.Unmarshal(body, &todo) // 第2引数にはデコード結果を格納する構造体のアドレス(ポインタ)を渡す
if err != nil {
log.Fatalf("JSONのデコードに失敗しました: %v", err)
}
// デコード結果の構造体の内容を出力
fmt.Println("デコードされたデータ:")
fmt.Printf("UserID: %d\n", todo.UserID)
fmt.Printf("ID: %d\n", todo.ID)
fmt.Printf("Title: %s\n", todo.Title)
fmt.Printf("Completed: %t\n", todo.Completed)
}
json.Unmarshal
関数は、第1引数にJSONデータ(バイトスライス)、第2引数にデコード結果を格納する変数のポインタを取ります。エラーが発生しなければ、todo
変数にJSONデータの内容が格納されます。
4. JSONデータの整形と利用 🛠️
構造体にデコードされたデータは、Goのプログラム内で自由に扱うことができます。必要な情報だけを取り出したり、他のデータと組み合わせたり、計算したりすることが可能です。
// ... (前のコードの続き) ...
// デコードされたデータを使って情報を整形して表示
fmt.Println("\n整形された情報:")
status := "未完了"
if todo.Completed {
status = "完了"
}
fmt.Printf("タスク「%s」(ID: %d) は%sです。\n", todo.Title, todo.ID, status)
// 例えば、特定の条件を満たすかチェック
if !todo.Completed {
fmt.Println("このタスクはまだ完了していません。")
}
このように、デコード後のデータをGoの変数として扱えるため、条件分岐(if
文)や文字列フォーマット(fmt.Printf
)などを活用して、目的に合わせたデータ整形や処理を行うことができます。
5. POSTリクエストとJSONエンコード 📤
データを取得するだけでなく、APIにデータを送信する場合もあります。よく使われるのがHTTP POSTリクエストでJSONデータを送信する方法です。Goの構造体をJSONデータ(バイト列)に変換する処理をエンコードまたはマーシャリングと呼び、encoding/json
パッケージのMarshal
関数を使います。
package main
import (
"bytes" // バイト列を扱うために必要
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
// 送信するデータ構造
type NewTodo struct {
UserID int `json:"userId"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
func main() {
apiURL := "https://jsonplaceholder.typicode.com/todos" // POST先のエンドポイント
// 送信するデータを作成
newTodo := NewTodo{
UserID: 1,
Title: "GoでPOSTリクエストの練習",
Completed: false,
}
// Goの構造体をJSONデータ(バイト列)にエンコード(マーシャリング)
jsonData, err := json.Marshal(newTodo)
if err != nil {
log.Fatalf("JSONへのエンコードに失敗しました: %v", err)
}
fmt.Printf("送信するJSONデータ: %s\n", string(jsonData))
// HTTP POSTリクエストを作成
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
if err != nil {
log.Fatalf("リクエストの作成に失敗しました: %v", err)
}
// Content-Typeヘッダーを設定 (JSONデータを送信することを示す)
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
// HTTPクライアントを作成してリクエストを送信
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("POSTリクエストの送信に失敗しました: %v", err)
}
defer resp.Body.Close()
// レスポンスを確認
fmt.Printf("レスポンスステータス: %s\n", resp.Status)
body, _ := io.ReadAll(resp.Body)
fmt.Printf("レスポンスボディ: %s\n", string(body)) // 作成されたリソースの情報などが返ってくることが多い
}
このコードでは、まず送信したいデータをNewTodo
構造体で定義し、json.Marshal
でJSONバイト列に変換します。次にhttp.NewRequest
でPOSTリクエストオブジェクトを作成し、bytes.NewBuffer
を使ってJSONデータをリクエストボディに設定します。
重要なのは、Content-Type
ヘッダーをapplication/json
に設定することです。これにより、サーバーは送信されたデータがJSON形式であることを認識できます。
最後に、http.Client
を使ってリクエストを送信し、レスポンスを受け取ります。
http.Post(url, "application/json", bytes.NewBuffer(jsonData))
のように、より簡潔に書くこともできます。ただし、ヘッダーを細かく設定したい場合などはhttp.NewRequest
を使うのが一般的です。
6. エラーハンドリングの考慮事項 🤔
APIクライアントを実装する際には、様々なエラーが発生する可能性を考慮する必要があります。
- ネットワークエラー: サーバーに接続できない、タイムアウトするなど。
http.Get
やclient.Do
がエラーを返します。 - HTTPステータスコードエラー: サーバーがエラーを示すステータスコード (4xx, 5xx) を返す場合。
resp.StatusCode
をチェックして適切に対応します (例: 404 Not Found, 500 Internal Server Error)。 - JSONデコード/エンコードエラー: 受信したデータが期待するJSON形式でない、または構造体とのマッピングに失敗する場合。
json.Unmarshal
やjson.Marshal
がエラーを返します。 - API固有のエラー: APIによっては、成功時 (2xx) でもレスポンスボディ内にエラー情報を含める場合があります。APIのドキュメントを確認し、レスポンス内容に基づいたエラー処理が必要です。
堅牢なアプリケーションを開発するためには、これらのエラーケースを網羅的にハンドリングすることが重要です。エラーが発生した場合にログを出力したり、処理をリトライしたり、ユーザーに分かりやすいメッセージを表示したりするなどの対応を検討しましょう。
効果的なエラーハンドリング戦略については、例えばJSON:API仕様のエラー形式やGoogle JSON Style Guideのエラー形式などを参考に、一貫性のあるエラーレスポンス設計を検討するのも良いでしょう。
7. まとめ 🎉
このセクションでは、Goの標準パッケージnet/http
とencoding/json
を使って、JSON APIクライアントを実装し、データを整形する方法を学びました。
http.Get
やhttp.Post
(またはhttp.NewRequest
とclient.Do
) でAPIにリクエストを送信できる。io.ReadAll
でレスポンスボディを読み取る。json.Unmarshal
でJSONデータをGoの構造体にデコードできる。構造体タグ (`json:"..."`
) がマッピングに役立つ。json.Marshal
でGoの構造体をJSONデータにエンコードできる。- 適切なエラーハンドリング (ネットワーク、HTTPステータス、JSON処理、API固有) が重要。
これらの知識を使えば、様々な外部APIと連携するGoアプリケーションを作成できます。ぜひ実際のAPIを叩いて試してみてください!💪
コメント