Rails
WebAPI
JSON

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

モチベーション

翻訳: WebAPI 設計のベストプラクティス - Qiita

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

手段

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

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

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

サマリ

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

    共通メソッドを用意する

    使い方

    コントローラーの各メソッドでrender json:の代わりに呼び出します。

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

    実装方法

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

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

    json rendererは文字列を渡されると、文字列化をスキップします。

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

    JSON.pretty_generateで変換した、prettyなJSON文字列を渡します。

    as_jsonの補足

    as_jsonメソッドを呼ぶのは、ハッシュ以外のオブジェクト(ActiveRecordのインスタンスなど)をハッシュに変換するためです。to_jsonメソッドを呼ぶとJSON文字列に変換されます。rails consoleで試すと

    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に渡すと、ダブルクォートが二重になります。

    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に、次のように記述します。

    config.middleware.use PrettyJsonResponse
    

    実装方法

    2012年のHow can I "pretty" format my JSON output in Ruby on Rails? - Stack Overflowにある、伝統的な手法です。

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

    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を作っている人がいるので、使う場合は自作せずに、Gemを使うとよいでしょう。

    デメリット

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

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

    メリット

    • 環境ごとに設定できる
    • 全てのレスポンスを一括で変更できる

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

    Rendererを追加する

    使い方

    コントローラーのメソッドで、他のrendererと同様に呼び出します。

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

    実装方法

    config/initializers/application_controller_renderer.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を作っている人がいるので、使う場合は自作せずに、Gemを使うとよいでしょう。

    デメリット

    • メソッド単位での変更が必要

    メリット

    • 見た目がrender json: objに似ていて、かっこいい