ti-enxame.com

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, chamo to_json e meu JSON está em uma linha. Às vezes, isso pode ser difícil de ver se houver um problema no fluxo de saída do JSON.

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

578
JP Richardson

Use a função pretty_generate(), incorporada 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"
}
918
jpatokal

Graças ao Rack Middleware e 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 recebo JSON bem impresso no navegador e na saída 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 cabeçalho '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)

70
gertas

A tag <pre> em HTML, usada com JSON.pretty_generate, renderizará o JSON em sua visão. Fiquei tão feliz quando meu ilustre chefe me mostrou isso:

<% if @data.present? %>
   <pre><%= JSON.pretty_generate(@data) %></pre>
<% end %>
65
Roger Garza

Se você quiser:

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

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
20
Ed Lebert

Confira 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 cores que o Stack Overflow não mostrará a você :)

11
Synthead

Usando <pre> 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] 
%>

<pre>
  <%=  JSON.pretty_generate(hash) %>
</pre>
10
oj5th

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

pp User.first.as_json

# => {
 "id" => 1,
 "first_name" => "Polar",
 "last_name" => "Bear"
}
10
Thomas Klemm

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

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

Como o pp de Ruby, ele manterá os objetos e as matrizes em uma linha quando couberem, 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 objetos? Você quer classificar as chaves do seu objeto? Você quer que os dois pontos estejam alinhados?

10
Phrogz

Aqui está uma solução de middleware modificada de esta 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 em ASCIIcasts 151: Rack Middleware por 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.

6
Wayne Conrad
#At Controller
def branch
    @data = Model.all
    render json: JSON.pretty_generate(@data.as_json)
end

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

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

By the way, a jóia é chamada 'api_Explorer' ( http://www.github.com/toptierlabs/api_Explorer )

2
Tony

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
2
sealocal

Aqui está a minha solução que eu derivou 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
2
Christopher Mullins

Eu uso o seguinte como eu acho os cabeçalhos, 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
1
TheDadman

Se você está usando RABL você pode configurá-lo como 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
1
Jim Flood

# example of use:
a_hash = {user_info: {type: "query_service", e_mail: "[email protected]", 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
1
Sergio Belevskij

Variante de impressão bonita:

my_object = { :array => [1, 2, 3, { :sample => "hash"}, 44455, 677778, 9900 ], :foo => "bar", rrr: {"pid": 63, "state": false}}
puts my_object.as_json.pretty_inspect.gsub('=>', ': ')

Resultado:

{"array": [1, 2, 3, {"sample": "hash"}, 44455, 677778, 9900],
 "foo": "bar",
 "rrr": {"pid": 63, "state": false}}
0
SergA