Como "JSON" no formato JSON em Ruby on Rails

626

Gostaria que minha saída JSON no Ruby on Rails fosse "bonita" ou bem formatada.

No momento, eu ligo to_jsone meu JSON está tudo em uma linha. Às vezes, pode ser difícil ver se há um problema no fluxo de saída JSON.

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

JP Richardson
fonte
2
Não tenho certeza de onde você está olhando, mas no console do webkit, ele cria uma boa árvore a partir de qualquer JSON registrado ou solicitado.
21919 Ryan Florence
8
Uma coisa a lembrar ao fazer isso é que o tamanho do seu conteúdo JSON aumentará devido ao espaço em branco adicional. Em um ambiente de desenvolvimento, geralmente é útil ter o JSON fácil de ler, mas em um ambiente de produção você deseja que seu conteúdo seja o mais enxuto possível, para velocidade e capacidade de resposta no navegador do usuário.
the Tin Man
2
O uso y my_jsonirá formatar muito bem as coisas, se você quiser uma solução rápida.
Aleatório
5
@randomorundefined method 'y' for main:Object
nurettin
yestá disponível no console de trilhos.
Sophia Feng

Respostas:

999

Use a pretty_generate()função, criada 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 você recebe:

{
  "array": [
    1,
    2,
    3,
    {
      "sample": "hash"
    }
  ],
  "foo": "bar"
}
lambshaanxy
fonte
32
Bacana! Eu coloquei isso no meu ~ / .irbrc: def json_pp (json) coloca JSON.pretty_generate (JSON.parse (json)) final
TheDeadSerious
10
Para tornar isso útil no Rails, parece que você deve dar uma resposta que inclua um código que possa ser usado no mesmo contexto queformat.json { render :json => @whatever }
iconoclast
9
Certamente prettyprinting deve ser usado apenas para depuração do lado do servidor? Se você colocar o código acima em um controlador, terá toneladas de espaço em branco inútil em todas as respostas, o que nem é necessário para a depuração do lado do cliente, pois qualquer ferramenta que valha a pena (por exemplo, Firebug) já lida com JSON de impressão bonita.
lambshaanxy
8
@jpatokal: você pode considerar que existem outras opções melhores, mas a questão era como fazer isso funcionar no Rails. Dizer "você não quer fazer isso no Rails" é uma não resposta. Obviamente, muitas pessoas querem fazer isso no Rails.
Iconoclast
39
O pôster original não dizia nada sobre onde, em um aplicativo Rails, ele deseja usar isso, então eu respondi com uma linha de Ruby que funcionará em qualquer lugar. Para usá-lo para gerar a resposta JSON em um Rails controlador , você já respondeu à sua própria pergunta: format.json { render :json => JSON.pretty_generate(my_json) }.
lambshaanxy
78

Graças ao Rack Middleware e ao Rails 3, você pode gerar bastante JSON 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 curlsaída.

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 no app/middleware/pretty_json_response.rbseu projeto Rails. E a etapa final é registrar o middleware em config/environments/development.rb:

config.middleware.use PrettyJsonResponse

Não recomendo usá-loproduction.rb . A correção JSON pode degradar 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 de curvatura manual sob demanda.

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

gertas
fonte
2
Como você está contornando a redefinição de to_json do ActiveSupport? Isso me impede de imprimir bastante enquanto o ActiveSupport está presente.
Munição Goettsch
1
Eu realmente não me importo, to_json, as_json, jbuilder que eu uso principalmente - qualquer que seja, o middleware transforma qualquer saída JSON. Eu tento evitar abrir aulas sempre que possível.
gertas
1
Eu tive que mudar a linha de análise obj = JSON.parse(response.body.first)para fazê-lo funcionar.
Kimmo Lehto
5
Funciona muito bem no Rails 4 também ... obrigado! Eu prefiro isso aos métodos mais específicos da biblioteca (como na resposta aceita). Como você só deve usar isso no modo dev, o impacto no desempenho não é grande coisa.
elsurudo
3
No Rails 5, tive que mudar Rack::Utils.bytesize(pretty_str).to_spara pretty_str.bytesize.to_se funciona muito bem!
Panteo
78

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

<% if @data.present? %>
   <pre><%= JSON.pretty_generate(@data) %></pre>
<% end %>
Roger Garza
fonte
5
Tão limpo e conciso!
Sean Szurko
23

Se você quiser:

  1. Pretifique todas as respostas JSON de saída do seu aplicativo automaticamente.
  2. Evite poluir o Objeto # to_json / # as_json
  3. Evite analisar / renderizar JSON usando middleware (YUCK!)
  4. Faça o caminho dos trilhos!

Então ... substitua o ActionController :: Renderer for 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
Ed Lebert
fonte
Isto é incrível, mas ele realmente faz com datas / horas para processar diferente: gist.github.com/nornagon/9c24b68bd6d3e871add3
nornagon
Vários problemas com isso: (1) JSON.pretty_generate requer json.respond_to?(:to_h)or :to_hash. (2) pretty_generate pode engasgar com coisas que to_json não.
Christopher Oezbek
@nornagon Não apliquei essa alteração e estou obtendo a mesma diferença que você viu entre .to_json e pretty_generate. Eu só o vejo em um console de trilhos, não em um simples IRB. Eu acho que isso pode ser uma coisa geral dos trilhos, nada a ver com esse patch. Além disso, Time.parse retorna o mesmo resultado quando você converte a string de volta ao tempo para os dois formatos. Seria apenas um pequeno inconveniente ao pesquisar registros de registro de data e hora, mas se você estiver cumprimentando mesmo assim, adicionar alguns \ s + não é realmente um grande problema.
con-- 27/09/18
@nornagon parece que o problema que você viu foi a redefinição de to_json do ActiveSupport, conforme mencionado no comentário de Ammo Goettsch
com--
17

Confira Impressão impressionante . Analise a string JSON em um Ruby Hash e exiba-a da seguinte apforma:

require "awesome_print"
require "json"

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

ap(JSON.parse(json))

Com o exposto, 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á.

Synthead
fonte
2
Concordo com você! awesome_print é simples demais!
Aashish
2
Nós estamos usando awesome_print também para nossos projetos e ele funciona como o nome é -> awesome
Simon Franzen
13

Despejando um objeto ActiveRecord no JSON (no console do Rails):

pp User.first.as_json

# => {
 "id" => 1,
 "first_name" => "Polar",
 "last_name" => "Bear"
}
Thomas Klemm
fonte
3
para obter uma sequência de caracteres em ppvez de imprimir na saída padrão, use User.first.as_json.pretty_inspect. Funciona bem para mim.
Johnny Wong
12

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>
oj5th
fonte
12

Se você achar que a pretty_generateopção incorporada na biblioteca JSON do Ruby não é "bonita" o suficiente, recomendo minha própria gema NeatJSON para sua formatação.

Para usá-lo:

gem install neatjson

e depois use

JSON.neat_generate

ao invés de

JSON.pretty_generate

Assim como o Ruby, ppele mantém objetos e matrizes em uma linha quando eles se ajustam, mas se agrupa em múltiplos 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 dos dois pontos? Antes / depois das vírgulas? Dentro dos colchetes de matrizes e objetos? Deseja classificar as chaves do seu objeto? Deseja que todos os dois pontos estejam alinhados?

Phrogz
fonte
2
Esta gema é incrível - o alinhamento dos dois pontos é particularmente doce!
Webdevguy
8

Aqui está uma solução de middleware modificada a partir desta excelente resposta do @gertas . Esta solução não é específica do Rails - ela deve funcionar com qualquer aplicativo Rack.

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

Este código está disponível 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, adicione-o a config / environment / test.rb e config / environment / development.rb:

config.middleware.use "PrettyJsonResponse"

Como o @gertas adverte em sua versão desta solução, evite usá-lo na produção. Está um pouco lento.

Testado com Rails 4.1.6.

Wayne Conrad
fonte
5
#At Controller
def branch
    @data = Model.all
    render json: JSON.pretty_generate(@data.as_json)
end
Буянбат Чойжилсүрэн
fonte
4

Aqui está minha solução, derivada de outros posts durante minha própria pesquisa.

Isso permite enviar 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
Christopher Mullins
fonte
3

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

Eu usei em uma gema que pode ser usada para depurar APIs de trilhos e funciona muito bem.

A propósito, a gema é chamada de 'api_explorer' ( http://www.github.com/toptierlabs/api_explorer )

Tony
fonte
3

Se você deseja 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
sealocal
fonte
2

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 ao usar JSON.pretty_generate é que os validadores de esquema JSON não ficarão mais satisfeitos com suas seqüências de data e hora. Você pode corrigir aqueles 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
Jim Flood
fonte
2

# 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
Sergio Belevskij
fonte
1

Uso o seguinte, pois considero os cabeçalhos, o status e a saída JSON úteis 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
TheDadman
fonte
1

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}}
SergA
fonte
0

Exemplo mais simples, eu poderia pensar em:

my_json = '{ "name":"John", "age":30, "car":null }'
puts JSON.pretty_generate(JSON.parse(my_json))

Exemplo de console do Rails:

core dev 1555:0> my_json = '{ "name":"John", "age":30, "car":null }'
=> "{ \"name\":\"John\", \"age\":30, \"car\":null }"
core dev 1556:0> puts JSON.pretty_generate(JSON.parse(my_json))
{
  "name": "John",
  "age": 30,
  "car": null
}
=> nil
Martin Carstens
fonte