Ruby on RailsのJSONレスポンスをprettifyする3つの方法


# モチベーション

[翻訳: WebAPI 設計のベストプラクティス - Qiita](http://qiita.com/mserizawa/items/b833e407d89abd21ee72#json-%E3%81%AF%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%A7%E6%95%B4%E5%BD%A2%E3%81%97%E3%82%88%E3%81%86)

> JSON はデフォルトで整形しよう
圧縮された状態の JSON をブラウザ上で見るのは、決して気持ちの良いものではありません

# 手段

大きく分けて３つの方法があります。

1. 共通メソッドを用意する
2. Rack Middlewareを使う
3. Rendererを追加する

それぞれメリットとデメリットがあります。

### サマリ

| 方法                 | メリット                            | デメリット                                                                                                              |
| ------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| 共通メソッドを用意する        | 実装がわかりやすい                       | <ol><li>見た目がrender json: objと変わり、美しくない</li><li>主な関心ごとでないソースコードが、アプリケーションのソースコードに混ざる</li><li>メソッド単位での変更が必要</li><ol> |
| Rack Middlewareを使う | 環境ごとに設定できる<br>全てのレスポンスを一括で変更できる | <ol><li>環境ごとに設定が必要</li><li>Railsが一度JSON文字列にしたものを、Rack Middlewareでパースしてオブジェクトにしてから、再度JSON文字列に戻す</li></ol>           |
| Rendererを追加する      | 見た目がrender json: objに似ていて、かっこいい | メソッド単位での変更が必要                                                                                                      |




## 共通メソッドを用意する
### 使い方
コントローラーの各メソッドで`render json:`の代わりに呼び出します。

```rb
class ExampleController < ActionController
  def show
    pretty_json { key: 'value' }
  end
end
```

### 実装方法

ApplicationControllerなどに共通メソッドを用意します。

```rb
class ApplicationController < ActionController
  def pretty_json(obj, options = nil)
    render json: JSON.pretty_generate(obj.as_json(options))
  end
end
```

[json renderer](https://github.com/rails/rails/blob/d0d7ce001bdbb0e5551da881caf8156c06238b84/actionpack/lib/action_controller/metal/renderers.rb#L157)は文字列を渡されると、文字列化をスキップします。

```rb
json = json.to_json(options) unless json.kind_of?(String)
```

[JSON.pretty_generate](http://ruby-doc.org/stdlib-2.5.0/libdoc/json/rdoc/JSON.html)で変換した、prettyなJSON文字列を渡します。

#### as_jsonの補足
[as_json](http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html)メソッドを呼ぶのは、ハッシュ以外のオブジェクト（ActiveRecordのインスタンスなど）をハッシュに変換するためです。`to_json`メソッドを呼ぶとJSON文字列に変換されます。rails consoleで試すと

```rb
irb(main):017:0> true.as_json
=> true
irb(main):018:0> true.to_json
=> "true"
```

`as_json`は真理値を返しますが、`to_json`は文字列に変換します。
このため、`to_json`した値を、JSON.pretty_generateに渡すと、ダブルクォートが二重になります。

```rb
irb(main):019:0> JSON.pretty_generate true.as_json
=> "true"
irb(main):020:0> JSON.pretty_generate true.to_json
=> "\"true\""
```





### デメリット
- 見た目が`render json: obj`と変わり、美しくない
- 主な関心ごとでないソースコードが、アプリケーションのソースコードに混ざる
- メソッド単位での変更が必要

### メリット
- 実装がわかりやすい 


## Rack Middlewareを使う
### 使い方
`config/environments/development.rb`に、次のように記述します。

```rb
config.middleware.use PrettyJsonResponse
```

### 実装方法
2012年の[How can I "pretty" format my JSON output in Ruby on Rails? - Stack Overflow](https://stackoverflow.com/a/13029297/1276969)にある、伝統的な手法です。

次のようなRack Middlewareを作ります。

```rb
class PrettyJsonResponse
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, response = @app.call(env)
    if headers["Content-Type"] =~ /^application\/json/
      obj = JSON.parse(response.body)
      pretty_str = JSON.pretty_unparse(obj)
      response = [pretty_str]
      headers["Content-Length"] = pretty_str.bytesize.to_s
    end
    [status, headers, response]
  end
end
```


Gem [rails_pretty_json](https://github.com/zeiv/rails_pretty_json)を作っている人がいるので、使う場合は自作せずに、Gemを使うとよいでしょう。

### デメリット
- 環境ごとに設定が必要
- Railsが一度JSON文字列にしたものを、Rack Middlewareでパースしてオブジェクトにしてから、再度JSON文字列に戻す

後者がパフォーマンス的にどれぐらいの影響があるかはわかりません。何となく気持ち悪いです。

### メリット
- 環境ごとに設定できる
- 全てのレスポンスを一括で変更できる

「development環境は設定し、productionは設定しない」というような使い方に向いています。

## Rendererを追加する
### 使い方
コントローラーのメソッドで、他のrendererと同様に呼び出します。

```rb
class ExampleController < ActionController
  def show
    render pretty_json: { key: 'value' }
  end
end
```

### 実装方法

`config/initializers/application_controller_renderer.rb`に

```rb
ActionController::Renderers.add :pretty_json do |obj, options|
  _render_with_renderer_json JSON.pretty_generate(obj.as_json(options)), options
end
```

を追加します。
`_render_with_renderer_json`は、`render json:`した時に実行されるメソッドです。
処理の内容は「共通メソッドを用意する」と同じです。



これもGem [rails_pretty_json_rednerer](https://github.com/ledsun/rails_pretty_json_rednerer)を作っている人がいるので、使う場合は自作せずに、Gemを使うとよいでしょう。

### デメリット
- メソッド単位での変更が必要

### メリット
- 見た目が`render json: obj`に似ていて、かっこいい