【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"`の１行を入れれば、JSONフォーマットを指定できます。

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

### JavaScriptの例（.jsonを付加する場合）
JavaScriptでは、次のように、open()メソッドのURL指定（第２引数）のところで`"/messages.json"`とします。

```js: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の実装](https://qiita.com/t-yama-3/items/7148d25b17e70ceb48d6)を参照してください。

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

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

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

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

```html:RequestHeaders
Accept: application/json
```

この`Acceptリクエストヘッダ`は、クライアントが理解できるコンテンツタイプ（MIMEタイプ）をサーバに伝えるものです（参照情報：[MDN Web Docs / Accept](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Accept)）。

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

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

```js:
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 {
          // リクエストが失敗した場合の処理を記載
        }
      }
    };
  });
});
```

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