外部の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を叩いて試してみてください!