Come "grazioso" formattare l'output JSON in Ruby on Rails


626

Vorrei che il mio output JSON in Ruby on Rails fosse "carino" o ben formattato.

In questo momento, chiamo to_jsone il mio JSON è tutto su una linea. A volte questo può essere difficile vedere se c'è un problema nel flusso di output JSON.

Esiste un modo per configurare per rendere il mio JSON "carino" o ben formattato in Rails?


2
Non sono sicuro di dove lo stai guardando, ma nella console di webkit crea un bel albero da qualsiasi JSON registrato o richiesto.
Ryan Florence,

8
Una cosa da ricordare quando si esegue questa operazione è che le dimensioni del contenuto JSON aumenteranno a causa dello spazio bianco aggiuntivo. In un ambiente di sviluppo è spesso utile avere JSON di facile lettura, ma in un ambiente di produzione si desidera che i contenuti siano più snelli che è possibile ottenere per velocità e reattività nel browser dell'utente.
Tin Man,

2
l'uso y my_jsonformatterà bene le cose se vuoi una soluzione rapida.
casuale

5
@randomorundefined method 'y' for main:Object
nurettin

yè disponibile nella console di rotaie.
Sophia Feng,

Risposte:


999

Utilizzare la pretty_generate()funzione, integrata nelle versioni successive di JSON. Per esempio:

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

Che ti dà:

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

32
Nifty! L'ho inserito nel mio ~ / .irbrc: def json_pp (json) mette fine JSON.pretty_generate (JSON.parse (json))
TheDeadSerious

10
Per renderlo utile in Rails, sembra che dovresti dare una risposta che includa il codice che può essere usato nello stesso contesto diformat.json { render :json => @whatever }
iconoclast

9
Sicuramente prettyprinting dovrebbe essere usato solo per il debug sul lato server? Se inserisci il codice sopra in un controller, avrai tonnellate di spazio bianco inutile in tutte le risposte, che non è nemmeno necessario per il debug sul lato client poiché qualsiasi strumento degno di nota (ad es. Firebug) gestisce già JSON piuttosto stampabile.
lambshaanxy,

8
@jpatokal: potresti considerare che ci sono altre opzioni migliori, ma la domanda era come farlo funzionare su Rails. Dire "non vuoi farlo in Rails" è una non risposta. Ovviamente molte persone vogliono farlo su Rails.
iconoclasta,

39
Il poster originale non diceva nulla su dove in un'app Rails voleva usarlo, quindi ho risposto con una linea di Ruby che funzionerà ovunque. Per utilizzarlo per generare la risposta JSON in un Rails regolatore , che già risposto alla tua domanda: format.json { render :json => JSON.pretty_generate(my_json) }.
lambshaanxy,

78

Grazie a Rack Middleware e Rails 3 puoi produrre abbastanza JSON per ogni richiesta senza cambiare alcun controller della tua app. Ho scritto questo frammento di middleware e ottengo JSON ben stampato nel browser e curlnell'output.

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

Il codice sopra deve essere inserito nel app/middleware/pretty_json_response.rbtuo progetto Rails. E il passaggio finale è registrare il middleware in config/environments/development.rb:

config.middleware.use PrettyJsonResponse

Non consiglio di usarlo inproduction.rb . Il reparsing JSON può ridurre i tempi di risposta e il throughput dell'app di produzione. Eventuali logiche extra come l'intestazione 'X-Pretty-Json: true' possono essere introdotte per attivare la formattazione per richieste di arricciatura manuali su richiesta.

(Testato con Rails 3.2.8-5.0.0, Ruby 1.9.3-2.2.0, Linux)


2
Come stai aggirando la ridefinizione di to_json di ActiveSupport? Questo mi impedisce di stampare piuttosto mentre ActiveSupport è presente.
Ammo Goettsch,

1
Non mi interessa davvero, to_json, as_json, jbuilder che utilizzo principalmente - qualunque cosa, il middleware trasforma qualsiasi output JSON. Cerco di evitare di aprire le lezioni quando possibile.
gertas,

1
Ho dovuto cambiare la linea di analisi obj = JSON.parse(response.body.first)per farlo funzionare.
Kimmo Lehto,

5
Funziona benissimo anche su Rails 4 ... grazie! Preferisco questo ai metodi più specifici della biblioteca (come nella risposta accettata). Dal momento che dovresti usarlo solo in modalità dev, il colpo di performance non è un grosso problema.
elsurudo,

3
In Rails 5 ho dovuto cambiare Rack::Utils.bytesize(pretty_str).to_sper pretty_str.bytesize.to_se funziona benissimo!
Panteo,

78

Il <pre>tag in HTML, usato con JSON.pretty_generate, renderà il JSON piuttosto bello nella tua vista. Ero così felice quando il mio illustre capo mi ha mostrato questo:

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

5
Così pulito e conciso!
Sean Szurko,

23

Se lo desidera:

  1. Imposta automaticamente tutte le risposte JSON in uscita dalla tua app.
  2. Evitare di inquinare l'oggetto # to_json / # as_json
  3. Evitare di analizzare / ripetere il rendering di JSON utilizzando il middleware (YUCK!)
  4. Fallo in RAILS WAY!

Quindi ... sostituisci ActionController :: Renderer per JSON! Aggiungi il seguente codice al tuo 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

Questo è fantastico, ma in realtà causa il rendering di date / orari in modo diverso: gist.github.com/nornagon/9c24b68bd6d3e871add3
nornagon

Diversi problemi con questo: (1) JSON.pretty_generate richiede json.respond_to?(:to_h)o :to_hash. (2) pretty_generate può soffocare su cose che to_json non ha.
Christopher Oezbek,

@nornagon Non ho applicato questa modifica e sto ottenendo la stessa differenza che hai visto tra .to_json e pretty_generate. Lo vedo solo in una console di binari, non semplicemente in irb. Penso che questa potrebbe essere una cosa generale per le rotaie, niente a che fare con questa patch. Inoltre, Time.parse restituisce lo stesso risultato quando si converte la stringa indietro nel tempo per entrambi i formati. Sarebbe solo un piccolo inconveniente durante la ricerca dei registri per i timestamp, ma se stai comunque saltando aggiungendo qualche \ s + non è davvero un grosso problema.
con--

@nornagon sembra che il problema che hai riscontrato fosse la ridefinizione di to_json di ActiveSupport, come menzionato nel commento di Ammo Goettsch
con--

17

Dai un'occhiata a Stampa fantastica . Analizza la stringa JSON in un Ruby Hash, quindi visualizzala in questo apmodo:

require "awesome_print"
require "json"

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

ap(JSON.parse(json))

Con quanto sopra, vedrai:

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

Awesome Print aggiungerà anche un po 'di colore che Stack Overflow non ti mostrerà.


2
Sono d'accordo con te! awesome_print è semplicemente fantastico!
Aashish

2
Stiamo usando awesome_print anche per i nostri progetti e funziona come il nome è -> fantastico
Simon Franzen

13

Dumping di un oggetto ActiveRecord su JSON (nella console di Rails):

pp User.first.as_json

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

3
per ottenere una stringa ppinvece di stampare sullo standard output, usare User.first.as_json.pretty_inspect. Funziona bene per me.
Johnny Wong,

12

Utilizzando il <pre>codice HTML ed pretty_generateè un buon trucco:

<%
  require 'json'

  hash = JSON[{hey: "test", num: [{one: 1, two: 2, threes: [{three: 3, tthree: 33}]}]}.to_json] 
%>

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

12

Se scopri che l' pretty_generateopzione integrata nella libreria JSON di Ruby non è abbastanza "carina", ti consiglio il mio gioiello NeatJSON per la tua formattazione.

Per usarlo:

gem install neatjson

e quindi usare

JSON.neat_generate

invece di

JSON.pretty_generate

Come Ruby, ppmanterrà gli oggetti e le matrici su una linea quando si adattano, ma si avvolge in multipli secondo necessità. Per esempio:

{
  "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?"}
  ]
}

Supporta anche una varietà di opzioni di formattazione per personalizzare ulteriormente l'output. Ad esempio, quanti spazi prima / dopo i due punti? Virgole prima / dopo? All'interno delle parentesi di matrici e oggetti? Vuoi ordinare le chiavi del tuo oggetto? Vuoi che i due punti siano tutti allineati?


2
Questa gemma oscilla: l'allineamento sui due punti è particolarmente dolce!
webdevguy,

8

Ecco una soluzione middleware modificata da questa eccellente risposta di @gertas . Questa soluzione non è specifica di Rails: dovrebbe funzionare con qualsiasi applicazione Rack.

La tecnica del middleware usata qui, usando #each, è spiegata in ASCIIcasts 151: Rack Middleware di Eifion Bedford.

Questo codice va in 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

Per accenderlo, aggiungilo a config / ambienti / test.rb e config / ambienti / sviluppo.rb:

config.middleware.use "PrettyJsonResponse"

Come avverte @gertas nella sua versione di questa soluzione, evita di usarlo in produzione. È un po 'lento.

Testato con rotaie 4.1.6.



4

Ecco la mia soluzione che ho derivato da altri post durante la mia ricerca.

Ciò consente di inviare l'output di pp e jj a un file secondo necessità.

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

3

Ho usato la gemma CodeRay e funziona abbastanza bene. Il formato include i colori e riconosce molti formati diversi.

L'ho usato su una gemma che può essere utilizzata per il debug delle API delle rotaie e funziona abbastanza bene.

A proposito, la gemma si chiama 'api_explorer' ( http://www.github.com/toptierlabs/api_explorer )


3

Se stai cercando di implementarlo rapidamente in un'azione del controller Rails per inviare una risposta JSON:

def index
  my_json = '{ "key": "value" }'
  render json: JSON.pretty_generate( JSON.parse my_json )
end

2

Se si utilizza RABL, è possibile configurarlo come descritto qui per utilizzare 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

Un problema con l'utilizzo di JSON.pretty_generate è che i validatori dello schema JSON non saranno più soddisfatti delle stringhe datetime. Puoi sistemarli nel tuo config / initializer / rabl_config.rb con:

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

2

# 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

1

Uso quanto segue poiché trovo utili le intestazioni, lo stato e l'output JSON come set. La routine di chiamata è spiegata su raccomandazione da una presentazione di railscast a: 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

Variante di stampa carina:

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('=>', ': ')

Risultato:

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

0

L'esempio più semplice, potrei pensare a:

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

Esempio di console 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.