はじめに
GoでJSONを扱う場合、最初に出会うのがjson.Marshal
とjson.Unmarshal
だと思います。
しかし、これらはバッファにデータを格納したり、読み取ったりすることで実現されるのですが、Decoder
とEncoder
を使えば、リクエストボディのストリームから直接デコードしたり、エンコードしたりするため、一時的なバッファを作成する必要がなく、メモリ使用量を抑えることができます。
またコードも簡潔に書くことができるため可読性も上がります。
今回は、そんなDecoder
とEncoder
を使い方を確認します。
MarshalとUnmarshalを使用する場合
// POST /article
func PostArticleHandler(w http.ResponseWriter, req *http.Request){
// Header.GETの返り値はstringなのでAtoiで整数に変換
length, err := strconv.Atoi(req.Header.Get("Content-Length"))
if err != nil {
http.Error(w, "cannot get content length\n", http.StatusBadRequest)
return
}
reqBodyBuffer := make([]byte, length)
if _, err := req.Body.Read(reqBodyBuffer); !errors.Is(err, io.EOF) {
http.Error(w, "fail to get request body\n", http.StatusBadRequest)
return
}
defer req.Body.Close()
var reqArticle models.Article
if err := json.Unmarshal(reqBodyBuffer, &reqArticle); err != nil {
http.Error(w, "fail to decode json≠\n", http.StatusBadRequest)
return
}
jsonData, err := json.Marshal(reqArticle)
if err != nil {
http.Error(w, "fail to encode json\n", http.StatusInternalServerError)
return
}
w.Write(jsonData)
}
Decoderを使用する場合
// POST /article
func PostArticleHandler(w http.ResponseWriter, req *http.Request) {
var reqArticle models.Article
if err := json.NewDecoder(req.Body).Decode(&reqArticle); err != nil {
http.Error(w, "fail to decode json\n", http.StatusBadRequest)
}
article := reqArticle
json.NewEncoder(w).Encode(article)
}
ストリームから直接リクエストデータを取るようにしたことで、デコード前の「Content-Lengthヘッダフィールドの値からバイトスライスを作り、そこにリクエストボディの中身を書き込む」という操作がまるまるいらなくなっています。直接デコーダの Decode メソッドを呼び出すだけで済むため、コードの見通しがとても良くなりました。
Decoderを使用するステップ
Decoder(Unmarshal)
ステップ1:リクエストボディをデコーダーに渡す
json.NewDecoder
を使って、リクエストボディをデコードします。
var reqArticle models.Article
decoder := json.NewDecoder(req.Body)
ステップ2:デコードを実行する
Decodeメソッド
を使って、リクエストボディを構造体にデコードします。
if err := decoder.Decode(&reqArticle); err != nil {
http.Error(w, "fail to decode json\n", http.StatusBadRequest)
return
}
Encoder(Marshal)
ステップ1: レスポンスのContent-Typeを設定する
クライアントに返すレスポンスがJSON形式であることを示すため、Content-Type ヘッダーを設定します。
w.Header().Set("Content-Type", "application/json")
ステップ2:レスポンスボディをエンコードする
json.NewEncoder
を使って、構造体をJSON形式でレスポンスボディにエンコードします。
if err := json.NewEncoder(w).Encode(reqArticle); err != nil {
http.Error(w, "fail to encode json\n", http.StatusInternalServerError)
return
}