Como posso “bonito” formatar minha saída JSON em Ruby on Rails?

Eu gostaria que minha saída JSON em Ruby on Rails fosse “bonita” ou bem formatada.

No momento, eu chamo to_json e meu JSON está em uma linha. Às vezes, isso pode ser difícil de ver se houver um problema no stream de saída do JSON.

Existe uma maneira de configurar ou um método para tornar meu JSON “bonito” ou bem formatado no Rails?

Use a function pretty_generate() , construída em versões posteriores do JSON. Por exemplo:

 require 'json' my_object = { :array => [1, 2, 3, { :sample => "hash"} ], :foo => "bar" } puts JSON.pretty_generate(my_object) 

O que te deixa:

 { "array": [ 1, 2, 3, { "sample": "hash" } ], "foo": "bar" } 

Graças ao Rack Middleware and Rails 3, você pode produzir um JSON bonito para cada solicitação sem alterar nenhum controlador do seu aplicativo. Eu escrevi esse trecho de middleware e obtive um JSON bem impresso no navegador e na saída de curl .

 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 

O código acima deve ser colocado em app/middleware/pretty_json_response.rb do seu projeto Rails. E o passo final é registrar o middleware em config/environments/development.rb :

 config.middleware.use PrettyJsonResponse 

Eu não recomendo usá-lo em production.rb . O JSON reparsing pode reduzir o tempo de resposta e a taxa de transferência do seu aplicativo de produção. Eventualmente, uma lógica extra, como o header ‘X-Pretty-Json: true’, pode ser introduzida para acionar a formatação de solicitações manuais de solicitação sob demanda.

(Testado com Rails 3.2.8-5.0.0, Ruby 1.9.3-2.2.0, Linux)

A tag


em HTML, usada com JSON.pretty_generate , renderizará o JSON bonito em sua visualização. Fiquei tão feliz quando meu ilustre chefe me mostrou isso:

 < % if !@data.blank? %> 
< %= JSON.pretty_generate(@data) %>

< % end %>

Se você quiser:

  1. Aperfeiçoar todas as respostas JSON de saída do seu aplicativo automaticamente.
  2. Evite poluir o object # to_json / # as_json
  3. Evite analisar / renderizar novamente o JSON usando o middleware (YUCK!)
  4. Faça o caminho dos carrilhões!

Então … substitua o ActionController :: Renderer para JSON! Basta adicionar o seguinte código ao seu ApplicationController:

 ActionController::Renderers.add :json do |json, options| unless json.kind_of?(String) json = json.as_json(options) if json.respond_to?(:as_json) json = JSON.pretty_generate(json, options) end if options[:callback].present? self.content_type ||= Mime::JS "#{options[:callback]}(#{json})" else self.content_type ||= Mime::JSON json end end 

Descarregando um object ActiveRecord para JSON (no console Rails):

 pp User.first.as_json # => { "id" => 1, "first_name" => "Polar", "last_name" => "Bear" } 

Se você (como eu) achar que a opção pretty_generate embutida na biblioteca JSON do Ruby não é “bonita” o suficiente, eu recomendo minha própria gem NeatJSON para a sua formatação.

Para usá-lo gem install neatjson e, em seguida, use JSON.neat_generate vez de JSON.pretty_generate .

Como os pp de Ruby, ele manterá os objects e matrizes em uma linha quando eles se encheckboxrem, mas será ajustado para vários conforme necessário. Por exemplo:

 { "navigation.createroute.poi":[ {"text":"Lay in a course to the Hilton","params":{"poi":"Hilton"}}, {"text":"Take me to the airport","params":{"poi":"airport"}}, {"text":"Let's go to IHOP","params":{"poi":"IHOP"}}, {"text":"Show me how to get to The Med","params":{"poi":"The Med"}}, {"text":"Create a route to Arby's","params":{"poi":"Arby's"}}, { "text":"Go to the Hilton by the Airport", "params":{"poi":"Hilton","location":"Airport"} }, { "text":"Take me to the Fry's in Fresno", "params":{"poi":"Fry's","location":"Fresno"} } ], "navigation.eta":[ {"text":"When will we get there?"}, {"text":"When will I arrive?"}, {"text":"What time will I get to the destination?"}, {"text":"What time will I reach the destination?"}, {"text":"What time will it be when I arrive?"} ] } 

Ele também suporta uma variedade de opções de formatação para personalizar ainda mais sua saída. Por exemplo, quantos espaços antes / depois do cólon? Antes / depois das vírgulas? Dentro dos suportes de matrizes e objects? Você quer classificar as chaves do seu object? Você quer que os dois pontos estejam alinhados?

Confira o awesome_print . Analise a string JSON em um Hash Ruby e exiba-a com awesome_print da seguinte forma:

 require "awesome_print" require "json" json = '{"holy": ["nested", "json"], "batman!": {"a": 1, "b": 2}}' ap(JSON.parse(json)) 

Com o acima, você verá:

 { "holy" => [ [0] "nested", [1] "json" ], "batman!" => { "a" => 1, "b" => 2 } } 

O awesome_print também adicionará algumas colors que o Stack Overflow não mostrará a você 🙂

Usando o código


html e pretty_generate é um bom truque:

 < % require 'json' hash = JSON[{hey: "test", num: [{one: 1, two: 2, threes: [{three: 3, tthree: 33}]}]}.to_json] %> 
 < %= JSON.pretty_generate(hash) %> 

Aqui está uma solução de middleware modificada desta excelente resposta por @gertas . Esta solução não é específica do Rails – deve funcionar com qualquer aplicação Rack.

A técnica de middleware usada aqui, usando #each, é explicada no ASCIIcasts 151: Rack Middleware, de Eifion Bedford.

Este código vai em app / middleware / pretty_json_response.rb :

 class PrettyJsonResponse def initialize(app) @app = app end def call(env) @status, @headers, @response = @app.call(env) [@status, @headers, self] end def each(&block) @response.each do |body| if @headers["Content-Type"] =~ /^application\/json/ body = pretty_print(body) end block.call(body) end end private def pretty_print(json) obj = JSON.parse(json) JSON.pretty_unparse(obj) end end 

Para ativá-lo, inclua isto em config / environments / test.rb e config / environments / development.rb:

 config.middleware.use "PrettyJsonResponse" 

Como @gertas avisa em sua versão desta solução, evite usá-la na produção. É um pouco lento.

Testado com Rails 4.1.6.

Aqui está a minha solução que eu obtive de outras postagens durante minha própria pesquisa.

Isso permite que você envie a saída pp e jj para um arquivo, conforme necessário.

 require "pp" require "json" class File def pp(*objs) objs.each {|obj| PP.pp(obj, self) } objs.size < = 1 ? objs.first : objs end def jj(*objs) objs.each {|obj| obj = JSON.parse(obj.to_json) self.puts JSON.pretty_generate(obj) } objs.size <= 1 ? objs.first : objs end end test_object = { :name => { first: "Christopher", last: "Mullins" }, :grades => [ "English" => "B+", "Algebra" => "A+" ] } test_json_object = JSON.parse(test_object.to_json) File.open("log/object_dump.txt", "w") do |file| file.pp(test_object) end File.open("log/json_dump.txt", "w") do |file| file.jj(test_json_object) end 

Eu usei a gem CodeRay e funciona muito bem. O formato inclui colors e reconhece muitos formatos diferentes.

Eu usei isso em uma gem que pode ser usada para depurar APIs de rails e funciona muito bem.

By the way, a gema é chamada ‘api_explorer’ ( http://www.github.com/toptierlabs/api_explorer )

Se você estiver procurando implementar isso rapidamente em uma ação do controlador Rails para enviar uma resposta JSON:

 def index my_json = '{ "key": "value" }' render json: JSON.pretty_generate( JSON.parse my_json ) end 
 #At Controller def branch @data = Model.all render json: JSON.pretty_generate(@data.as_json) end 

Eu uso o seguinte como eu acho os headers, status e saída JSON útil como um conjunto. A rotina de chamadas é dividida por recomendação de uma apresentação de railscasts em: http://railscasts.com/episodes/151-rack-middleware?autoplay=true

  class LogJson def initialize(app) @app = app end def call(env) dup._call(env) end def _call(env) @status, @headers, @response = @app.call(env) [@status, @headers, self] end def each(&block) if @headers["Content-Type"] =~ /^application\/json/ obj = JSON.parse(@response.body) pretty_str = JSON.pretty_unparse(obj) @headers["Content-Length"] = Rack::Utils.bytesize(pretty_str).to_s Rails.logger.info ("HTTP Headers: #{ @headers } ") Rails.logger.info ("HTTP Status: #{ @status } ") Rails.logger.info ("JSON Response: #{ pretty_str} ") end @response.each(&block) end end 

Se você estiver usando o RABL, poderá configurá-lo conforme descrito aqui para usar JSON.pretty_generate:

 class PrettyJson def self.dump(object) JSON.pretty_generate(object, {:indent => " "}) end end Rabl.configure do |config| ... config.json_engine = PrettyJson if Rails.env.development? ... end 

Um problema com o uso de JSON.pretty_generate é que os validadores de esquema JSON não ficarão mais satisfeitos com suas cadeias de data e hora. Você pode consertar isso em seu config / initializers / rabl_config.rb com:

 ActiveSupport::TimeWithZone.class_eval do alias_method :orig_to_s, :to_s def to_s(format = :default) format == :default ? iso8601 : orig_to_s(format) end end 
 # example of use: a_hash = {user_info: {type: "query_service", e_mail: "my@email.com", phone: "+79876543322"}, cars_makers: ["bmw", "mitsubishi"], car_models: [bmw: {model: "1er", year_mfc: 2006}, mitsubishi: {model: "pajero", year_mfc: 1997}]} pretty_html = a_hash.pretty_html # include this module to your libs: module MyPrettyPrint def pretty_html indent = 0 result = "" if self.class == Hash self.each do |key, value| result += "#{key} : #{[Array, Hash].include?(value.class) ? value.pretty_html(indent+1) : value} " end elsif self.class == Array result = "[#{self.join(', ')}]" end "#{result}" end end class Hash include MyPrettyPrint end class Array include MyPrettyPrint end 

# example of use: a_hash = {user_info: {type: "query_service", e_mail: "my@email.com", phone: "+79876543322"}, cars_makers: ["bmw", "mitsubishi"], car_models: [bmw: {model: "1er", year_mfc: 2006}, mitsubishi: {model: "pajero", year_mfc: 1997}]} pretty_html = a_hash.pretty_html # include this module to your libs: module MyPrettyPrint def pretty_html indent = 0 result = "" if self.class == Hash self.each do |key, value| result += "#{key} : #{[Array, Hash].include?(value.class) ? value.pretty_html(indent+1) : value} " end elsif self.class == Array result = "[#{self.join(', ')}]" end "#{result}" end end class Hash include MyPrettyPrint end class Array include MyPrettyPrint end

# example of use: a_hash = {user_info: {type: "query_service", e_mail: "my@email.com", phone: "+79876543322"}, cars_makers: ["bmw", "mitsubishi"], car_models: [bmw: {model: "1er", year_mfc: 2006}, mitsubishi: {model: "pajero", year_mfc: 1997}]} pretty_html = a_hash.pretty_html # include this module to your libs: module MyPrettyPrint def pretty_html indent = 0 result = "" if self.class == Hash self.each do |key, value| result += "#{key} : #{[Array, Hash].include?(value.class) ? value.pretty_html(indent+1) : value} " end elsif self.class == Array result = "[#{self.join(', ')}]" end "#{result}" end end class Hash include MyPrettyPrint end class Array include MyPrettyPrint end