【Rails / JavaScript】レスポンスのフォーマットをJSONに指定する(.jsonを付加せずに)


コントローラーにおけるJSONフォーマットの指定

JavaScriptのみでAjaxを書く際に、レスポンスデータをJSONで受け取ることに苦労したので、備忘として書いておきます。

使用しているフレームワークは、Ruby on Raisです。
具体的には、次のようなコントローラーのrespond_toメソッドにおいて、format.jsonを選択させることが目的になります。

messages_controller.rb
def create
  @message = Message.new(message_params)
  if @message.save
    respond_to do |format|
      format.html { redirect_to root_path }
      format.json { render json: { text: @message.text, id: @message.id } }
    end
  end
end

この記事の結論を先に言うと、Acceptリクエストヘッダでレスポンスのフォーマットを指定するということになります(後述)。
分かっている方には、当たり前の内容かもしれません。

<実行した環境>
Rails 5.2.4.1
Ruby 2.5.1
(turbolinksは無効にしています)

一般的な指定の方法

まずは、一般的な指定の方法です。普通はこれで解決すると思います。

jQueryの場合

jQueryでは、dataType: "json"の1行を入れれば、JSONフォーマットを指定できます。

jQueryの例
$.ajax({
  url: "/messages",
  type: "POST",
  data: {
    message: {text: "hoge"}
  },
  dataType: "json"  // レスポンスフォーマットをjson形式と指定する
})

JavaScriptの例(.jsonを付加する場合)

JavaScriptでは、次のように、open()メソッドのURL指定(第2引数)のところで"/messages.json"とします。

JavaScriptの例(.jsonを付加)
xmlHttpRequest.open("POST", "/messages.json");  // urlの末尾に.jsonを付加することでレスポンスフォーマットを指定
xmlHttpRequest.responseType = "json";  // レスポンスデータの形式をjsonと指定(これはフォーマットの指定ではない)
xmlHttpRequest.setRequestHeader("Content-Type", "application/json");
xmlHttpRequest.setRequestHeader("X-CSRF-Token", token);
xmlHttpRequest.send(data);

このように、リクエストURLの末尾に.jsonを付加すれば、JSONのフォーマットでレスポンスを得ることができます。
なお、サンプルコードの全体像を確認されたい場合は、RailsにおけるAjaxの実装を参照してください。

<勘違いしていたところ>
2行目のresponseTypeメソッドで、xmlHttpRequest.responseType = "json"というように指定していますが、最初は、これをレスポンスのフォーマットの指定と勘違いしていました。

確認してみると、"json"と指定した場合のレスポンスデータは、{text: "hoge", id: 3}という形式で戻り、指定しなかった場合には、{"text":"hoge","id":3}という形で戻ってくるだけです。
つまり、responseTypeメソッドはフォーマットを指定するものではないということです。

リクエストヘッダで指定する方法

最後に、リクエストURLに、.jsonを付加しないでレスポンスのフォーマットを指定する方法です。

リクエストヘッダに次の1行を追加すれば、JSONのフォーマットでレスポンスデータが届きます。

RequestHeaders
Accept: application/json

このAcceptリクエストヘッダは、クライアントが理解できるコンテンツタイプ(MIMEタイプ)をサーバに伝えるものです(参照情報:MDN Web Docs / Accept)。

jQueryのリクエストヘッダに、上記の指定がされていたことから、JavaScriptでも同様の指定をすることでうまく動作しました。

このリクエストヘッダを追加するには、JavaScriptに次の1行を入れます。

xmlHttpRequest.setRequestHeader("Accept", "application/json");

念のため、コードの全体像を示せば次のとおりです。
リクエストURLへの.jsonの付加は不要となります。

sample.js
window.addEventListener("load", function() {
  let token = document.getElementsByName("csrf-token")[0].content; //セキュリティトークンの取得
  document.getElementById("nmessage_input").addEventListener("submit", function(e) {
    e.preventDefault();
    let hashData = { message: { text: "hoge" } };
    let data = JSON.stringify(hashData);
    let xmlHttpRequest = new XMLHttpRequest();  // XMLHttpRequestオブジェクトの作成
    xmlHttpRequest.open("POST", "/messages");  // urlの末尾への.jsonの付加は不要
    xmlHttpRequest.responseType = "json";
    xmlHttpRequest.setRequestHeader("Accept", "application/json");  // リクエストヘッダーを追加(クライアントが理解できるコンテンツタイプをサーバに通知)
    xmlHttpRequest.setRequestHeader("Content-Type", "application/json");  // リクエストヘッダーを追加(リクエストをJSONで送信する旨)
    xmlHttpRequest.setRequestHeader("X-CSRF-Token", token);  // リクエストヘッダーを追加(セキュリティトークンの追加)
    xmlHttpRequest.send(data);  // sendメソッドでサーバに送信
    xmlHttpRequest.onreadystatechange = function() {
      if (xmlHttpRequest.readyState === 4) {
        if (xmlHttpRequest.status === 200) {
          // リクエストが成功した場合の処理を記載
        } else {
          // リクエストが失敗した場合の処理を記載
        }
      }
    };
  });
});

以上です。
何らかのお役にたてば幸いです。