[Goのはじめ方] Part32: JSON APIクライアントとデータ整形

Go

外部のJSON APIからデータを取得し、必要な形に整形する方法を学びましょう。

多くのWebサービスやアプリケーションは、他のシステムとデータを交換するためにJSON APIを提供しています。 例えば、天気情報、ニュース記事、商品情報などを取得する際に利用されます。

このセクションでは、Goの標準パッケージを使ってJSON APIにリクエストを送り、取得したデータをGoのプログラムで扱いやすい形(構造体など)に変換(整形)する方法を学びます。 これにより、外部サービスと連携するアプリケーションを開発できるようになります。

まず、外部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() はリソースリークを防ぐために不可欠です。

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データの内容が格納されます。

💡 JSON to Go Struct: JSONレスポンスが複雑な場合、手動で構造体を定義するのは大変です。JSON-to-Goのようなオンラインツールを使うと、JSONデータからGoの構造体定義を自動生成できて便利です。

構造体にデコードされたデータは、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)などを活用して、目的に合わせたデータ整形や処理を行うことができます。

データを取得するだけでなく、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の簡易版: 単純なPOSTリクエストであれば、http.Post(url, "application/json", bytes.NewBuffer(jsonData))のように、より簡潔に書くこともできます。ただし、ヘッダーを細かく設定したい場合などはhttp.NewRequestを使うのが一般的です。

APIクライアントを実装する際には、様々なエラーが発生する可能性を考慮する必要があります。

  • ネットワークエラー: サーバーに接続できない、タイムアウトするなど。http.Getclient.Doがエラーを返します。
  • HTTPステータスコードエラー: サーバーがエラーを示すステータスコード (4xx, 5xx) を返す場合。resp.StatusCodeをチェックして適切に対応します (例: 404 Not Found, 500 Internal Server Error)。
  • JSONデコード/エンコードエラー: 受信したデータが期待するJSON形式でない、または構造体とのマッピングに失敗する場合。json.Unmarshaljson.Marshalがエラーを返します。
  • API固有のエラー: APIによっては、成功時 (2xx) でもレスポンスボディ内にエラー情報を含める場合があります。APIのドキュメントを確認し、レスポンス内容に基づいたエラー処理が必要です。

堅牢なアプリケーションを開発するためには、これらのエラーケースを網羅的にハンドリングすることが重要です。エラーが発生した場合にログを出力したり、処理をリトライしたり、ユーザーに分かりやすいメッセージを表示したりするなどの対応を検討しましょう。

効果的なエラーハンドリング戦略については、例えばJSON:API仕様のエラー形式Google JSON Style Guideのエラー形式などを参考に、一貫性のあるエラーレスポンス設計を検討するのも良いでしょう。

このセクションでは、Goの標準パッケージnet/httpencoding/jsonを使って、JSON APIクライアントを実装し、データを整形する方法を学びました。

  • http.Gethttp.Post (またはhttp.NewRequestclient.Do) でAPIにリクエストを送信できる。
  • io.ReadAllでレスポンスボディを読み取る。
  • json.UnmarshalでJSONデータをGoの構造体にデコードできる。構造体タグ (`json:"..."`) がマッピングに役立つ。
  • json.MarshalでGoの構造体をJSONデータにエンコードできる。
  • 適切なエラーハンドリング (ネットワーク、HTTPステータス、JSON処理、API固有) が重要。

これらの知識を使えば、様々な外部APIと連携するGoアプリケーションを作成できます。ぜひ実際のAPIを叩いて試してみてください!💪

コメント

タイトルとURLをコピーしました