Edited at

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に似ていて、かっこいい