JSONレスポンスの書き込み

Go標準ライブラリのnet/httpパッケージを利用して、JSON形式のレスポンスを返却するREST APIの作成方法を説明します。 エラー処理としてエンコードエラーのハンドリング方法についても説明します。

基本

jsonパッケージを利用することで、構造体のデータをJSONデータにエンコードできます。

次はHTTPレスポンスのボディにJSONデータを書き込むハンドラ関数です。

type Task struct {
    ID      string `json:"id"`
    Title   string `json:"title"`
    Done    bool   `json:"done"`
    Deleted bool   `json:"-"`
}

func getTask(w http.ResponseWriter, r *http.Request) {
    // ここにデータの取得処理があるとする

    // データがなかった場合
    if !found {
        // JSONデータを書き込む
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
        w.WriteHeader(http.StatusNotFound)
        s := fmt.Sprintf("%d %s", code, http.StatusText(code))
        m := map[string]string{"status": s}
        if err := json.NewEncoder(w).Encode(m); err != nil {
            log.Println(err)
        }
        return
    }

    t := Task{
        ID: "1",
        Title: "shopping",
    }
    // JSONデータを書き込む
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    w.WriteHeader(http.StatusOK)
    if err := json.NewEncoder(w).Encode(t); err != nil {
        log.Println(err)
    }
}

エラー処理の強化

先ほどのコードでは、次の場合にエラーとなります。

  • JSONのエンコードに失敗した場合
  • レスポンスの書き込みに失敗した場合

前者の場合、ステータスコード500をレスポンスするには、次の通り処理します。 ポイントは、JSONのエンコード処理とレスポンスの書き込み処理を別にして、 エンコードのエラー処理をレスポンスの書き込み処理の前にすることです。

w.Header().Set("Content-Type", "application/json; charset=utf-8")
// JSONのエンコード
b, err := json.Marshal(v)
if err != nil {
    log.Println(err)
    // JSONエンコード失敗のため、エラーをレスポンスする
    w.WriteHeader(http.StatusInternalServerError)
    s := `{"status":"500 Internal Server Error"}`
    if _, err := w.Write([]byte(s)); err != nil {
        log.Println(err)
    }
    return
}
// レスポンスの書き込み
w.WriteHeader(code)
if _, err := w.Write(b); err != nil {
    log.Println(err)
}

ヘルパー関数

JSONレスポンスの書き込みは各ハンドラで実行するため、ヘルパー関数を作成します。

func getTask(w http.ResponseWriter, r *http.Request) {
    // データの取得処理は割愛

    // データがなかった場合
    if !found {
        writeJSONError(w, http.StatusNotFound, "")
    }

    t := Task{
        ID: "1",
        Title: "shopping",
    }
    writeJSON(w, http.StatusOK, t)
}

func writeJSON(w http.ResponseWriter, code int, v interface{}) {
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    b, err := json.Marshal(v)
    if err != nil {
        log.Println(err)
        w.WriteHeader(http.StatusInternalServerError)
        s := `{"status":"500 Internal Server Error"}`
        if _, err := w.Write([]byte(s)); err != nil {
            log.Println(err)
        }
        return
    }
    w.WriteHeader(code)
    if _, err := w.Write(b); err != nil {
        log.Println(err)
    }
}

func writeJSONError(w http.ResponseWriter, code int, message string) {
    s := fmt.Sprintf("%d %s", code, http.StatusText(code))
    m := map[string]string{"status": s}
    if message != "" {
        m["message"] = message
    }
    writeJSON(w, code, m)
}