Comment «joliment» formater la sortie JSON dans Ruby on Rails

626

J'aimerais que ma sortie JSON dans Ruby on Rails soit "jolie" ou bien formatée.

En ce moment, j'appelle to_jsonet mon JSON est sur une seule ligne. Il peut parfois être difficile de voir s'il y a un problème dans le flux de sortie JSON.

Existe-t-il un moyen de configurer pour rendre mon JSON "joli" ou bien formaté dans Rails?

JP Richardson
la source
2
Je ne sais pas où vous le regardez, mais dans la console de webkit, cela crée un bel arbre à partir de tout JSON connecté ou demandé.
Ryan Florence
8
Une chose à retenir lors de cette opération est que la taille de votre contenu JSON montera en flèche à cause de l'espace supplémentaire. Dans un environnement de développement, il est souvent utile d'avoir le JSON facile à lire, mais dans un environnement de production, vous voulez que votre contenu soit aussi simple que possible pour obtenir vitesse et réactivité dans le navigateur de l'utilisateur.
le Tin Man
2
utiliser y my_jsonformatera bien les choses si vous voulez une solution rapide.
randomor
5
@randomorundefined method 'y' for main:Object
nurettin
yest disponible en console rails.
Sophia Feng

Réponses:

999

Utilisez la pretty_generate()fonction, intégrée dans les versions ultérieures de JSON. Par exemple:

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

Ce qui vous donne:

{
  "array": [
    1,
    2,
    3,
    {
      "sample": "hash"
    }
  ],
  "foo": "bar"
}
lambshaanxy
la source
32
Nifty! J'ai mis cela dans mon ~ / .irbrc: def json_pp (json) met fin JSON.pretty_generate (JSON.parse (json))
TheDeadSerious
10
Pour rendre cela utile dans Rails, il semble que vous devriez donner une réponse qui inclut du code qui peut être utilisé dans le même contexte queformat.json { render :json => @whatever }
iconoclast
9
Sûrement que la jolie impression ne devrait être utilisée que pour le débogage côté serveur? Si vous collez le code ci-dessus dans un contrôleur, vous aurez des tonnes d'espaces blancs inutiles dans toutes les réponses, ce qui n'est même pas nécessaire pour le débogage côté client, car tous les outils dignes de ce nom (par exemple, Firebug) gèrent déjà la jolie impression JSON.
lambshaanxy
8
@jpatokal: vous pouvez considérer qu'il existe d'autres meilleures options, mais la question était de savoir comment faire fonctionner cela dans Rails. Dire "vous ne voulez pas faire ça dans Rails" est une non-réponse. Évidemment, beaucoup de gens veulent le faire dans Rails.
iconoclaste du
39
L'affiche originale ne dit rien sur l' endroit où il veut utiliser une application Rails, j'ai donc répondu avec une ligne de Ruby qui fonctionnera n'importe où. Pour l' utiliser pour générer la réponse JSON dans un Rails contrôleur , vous avez déjà répondu à votre propre question: format.json { render :json => JSON.pretty_generate(my_json) }.
lambshaanxy
78

Grâce à Rack Middleware et Rails 3, vous pouvez générer un joli JSON pour chaque demande sans changer le contrôleur de votre application. J'ai écrit un tel extrait de middleware et j'obtiens un JSON bien imprimé dans le navigateur et la curlsortie.

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

Le code ci-dessus doit être placé dans app/middleware/pretty_json_response.rbvotre projet Rails. Et la dernière étape consiste à enregistrer le middleware dans config/environments/development.rb:

config.middleware.use PrettyJsonResponse

Je ne recommande pas de l'utiliser dansproduction.rb . La nouvelle analyse JSON peut dégrader le temps de réponse et le débit de votre application de production. Finalement, une logique supplémentaire telle que l'en-tête «X-Pretty-Json: true» peut être introduite pour déclencher le formatage des demandes de curl manuelles à la demande.

(Testé avec Rails 3.2.8-5.0.0, Ruby 1.9.3-2.2.0, Linux)

gertas
la source
2
Comment contournez-vous la redéfinition de to_json par ActiveSupport? Cela m'empêche de faire de jolies impressions lorsque ActiveSupport est présent.
Ammo Goettsch
1
Peu m'importe, to_json, as_json, jbuilder que j'utilise principalement - peu importe, le middleware transforme n'importe quelle sortie JSON. J'essaie d'éviter d'ouvrir des cours autant que possible.
gertas
1
J'ai dû changer la ligne d'analyse obj = JSON.parse(response.body.first)pour la faire fonctionner.
Kimmo Lehto
5
Fonctionne très bien dans Rails 4 aussi ... merci! Je préfère cela aux méthodes plus spécifiques à la bibliothèque (comme dans la réponse acceptée). Comme vous ne devriez utiliser cela qu'en mode développeur de toute façon, la performance n'est pas un gros problème.
elsurudo
3
Dans Rails 5 je devais changer Rack::Utils.bytesize(pretty_str).to_spour pretty_str.bytesize.to_set il fonctionne très bien!
panteo
78

La <pre>balise en HTML, utilisée avec JSON.pretty_generate, rendra le JSON joli dans votre vue. J'étais tellement heureux quand mon illustre patron m'a montré ceci:

<% if @data.present? %>
   <pre><%= JSON.pretty_generate(@data) %></pre>
<% end %>
Roger Garza
la source
5
Tellement propre et concis!
Sean Szurko
23

Si tu veux:

  1. Optimisez automatiquement toutes les réponses JSON sortantes de votre application.
  2. Évitez de polluer l'objet # to_json / # as_json
  3. Évitez d'analyser / de restituer JSON en utilisant un middleware (YUCK!)
  4. Faites-le de la manière RAILS!

Alors ... remplacez ActionController :: Renderer for JSON! Ajoutez simplement le code suivant à votre 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
la source
C'est génial, mais en fait, les dates / heures s'affichent
nornagon
Plusieurs problèmes avec ceci: (1) JSON.pretty_generate nécessite json.respond_to?(:to_h)ou :to_hash. (2) pretty_generate peut s'étouffer avec des choses que to_json ne fait pas.
Christopher Oezbek
@nornagon Je n'ai pas appliqué ce changement et j'obtiens la même différence que vous avez vue entre .to_json et pretty_generate. Je ne le vois que dans une console de rails, pas en irb simple. Je pense que cela pourrait être une chose générale, rien à voir avec ce patch. En outre, Time.parse renvoie le même résultat lorsque vous reconvertissez la chaîne en temps pour les deux formats. Ce ne serait qu'un inconvénient mineur lors de la recherche des horodatages dans les journaux, mais si vous saluez de toute façon, ajouter quelques \ s + n'est pas vraiment un problème.
con--
@nornagon ressemble au problème que vous avez vu était la redéfinition d'ActiveSupport de to_json, comme mentionné dans le commentaire d'Ammo Goettsch
con--
17

Découvrez Impression impressionnante . Analyser la chaîne JSON dans un hachage Ruby, puis l'afficher avec apcomme ceci:

require "awesome_print"
require "json"

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

ap(JSON.parse(json))

Avec ce qui précède, vous verrez:

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

Awesome Print ajoutera également des couleurs que Stack Overflow ne vous montrera pas.

Synthead
la source
2
D'accord avec toi! awesome_print est simple génial!
Aashish
2
Nous utilisons également awesome_print pour nos projets et cela fonctionne comme le nom est -> awesome
Simon Franzen
13

Vidage d'un objet ActiveRecord vers JSON (dans la console Rails):

pp User.first.as_json

# => {
 "id" => 1,
 "first_name" => "Polar",
 "last_name" => "Bear"
}
Thomas Klemm
la source
3
pour obtenir une chaîne au pplieu d'imprimer sur une sortie standard, utilisez User.first.as_json.pretty_inspect. Fonctionne bien pour moi.
Johnny Wong
12

Utiliser du <pre>code HTML et pretty_generatec'est une bonne astuce:

<%
  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
la source
12

Si vous trouvez que l' pretty_generateoption intégrée à la bibliothèque JSON de Ruby n'est pas assez "jolie", je recommande mon propre bijou NeatJSON pour votre mise en forme.

Pour l'utiliser:

gem install neatjson

puis utiliser

JSON.neat_generate

au lieu de

JSON.pretty_generate

Comme Ruby, ppil gardera les objets et les tableaux sur une seule ligne lorsqu'ils s'adapteront, mais en enroulera en plusieurs si nécessaire. Par exemple:

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

Il prend également en charge une variété d' options de formatage pour personnaliser davantage votre sortie. Par exemple, combien d'espaces avant / après deux-points? Avant / après des virgules? À l'intérieur des supports de tableaux et d'objets? Voulez-vous trier les clés de votre objet? Voulez-vous que les deux points soient alignés?

Phrogz
la source
2
Ce bijou bascule - l'alignement sur les deux points est particulièrement doux!
webdevguy
8

Voici une solution middleware modifiée de cette excellente réponse de @gertas . Cette solution n'est pas spécifique à Rails - elle devrait fonctionner avec n'importe quelle application Rack.

La technique du middleware utilisée ici, en utilisant #each, est expliquée dans ASCIIcasts 151: Rack Middleware par Eifion Bedford.

Ce code va dans 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

Pour l'activer, ajoutez-le à config / environnements / test.rb et config / environnements / development.rb:

config.middleware.use "PrettyJsonResponse"

Comme @gertas le prévient dans sa version de cette solution, évitez de l'utiliser en production. C'est un peu lent.

Testé avec Rails 4.1.6.

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

Voici ma solution que j'ai dérivée d'autres articles lors de ma propre recherche.

Cela vous permet d'envoyer la sortie pp et jj dans un fichier selon vos besoins.

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
la source
3

J'ai utilisé le joyau CodeRay et cela fonctionne plutôt bien. Le format comprend des couleurs et reconnaît de nombreux formats différents.

Je l'ai utilisé sur un joyau qui peut être utilisé pour le débogage des API de rails et cela fonctionne plutôt bien.

Soit dit en passant, la gemme est nommée 'api_explorer' ( http://www.github.com/toptierlabs/api_explorer )

Tony
la source
3

Si vous cherchez à implémenter rapidement ceci dans une action de contrôleur Rails pour envoyer une réponse JSON:

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

Si vous utilisez RABL, vous pouvez le configurer comme décrit ici pour utiliser 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 problème avec l'utilisation de JSON.pretty_generate est que les validateurs de schéma JSON ne seront plus satisfaits de vos chaînes datetime. Vous pouvez les corriger dans votre config / initializers / rabl_config.rb avec:

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
la source
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
la source
1

J'utilise ce qui suit car je trouve les en-têtes, l'état et la sortie JSON utiles en tant qu'ensemble. La routine d'appel est divisée sur recommandation d'une présentation de diffusion sur: 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
la source
1

Jolie variante d'impression:

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

Résultat:

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

Exemple le plus simple, je pourrais penser à:

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

Exemple de 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
Martin Carstens
la source