Jak „ładnie” sformatować wyjście JSON w Ruby on Rails

626

Chciałbym, aby moje wyjście JSON w Ruby on Rails było „ładne” lub ładnie sformatowane.

W tej chwili dzwonię, to_jsona mój JSON jest na jednej linii. Czasami może być trudno stwierdzić, czy występuje problem w strumieniu wyjściowym JSON.

Czy istnieje sposób, aby skonfigurować JSON jako „ładny” lub ładnie sformatowany w Railsach?

JP Richardson
źródło
2
Nie jestem pewien, gdzie na to patrzysz, ale w konsoli webkita tworzy ładne drzewo z dowolnego zalogowanego lub żądanego JSON.
Ryan Florence,
8
Należy przy tym pamiętać, że rozmiar zawartości JSON będzie się powiększał z powodu dodatkowych białych znaków. W środowisku programistycznym często pomocne jest, aby JSON był łatwy do odczytania, ale w środowisku produkcyjnym chcesz, aby zawartość była tak uboga, jak to możliwe, aby uzyskać szybkość i szybkość reakcji w przeglądarce użytkownika.
Tin Man
2
use y my_jsonładnie sformatuje rzeczy, jeśli chcesz szybkiej naprawy.
losowy
5
@randomorundefined method 'y' for main:Object
nurettin
yjest dostępny w konsoli szyn.
Sophia Feng

Odpowiedzi:

999

Użyj pretty_generate()funkcji wbudowanej w późniejszych wersjach JSON. Na przykład:

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

Co daje ci:

{
  "array": [
    1,
    2,
    3,
    {
      "sample": "hash"
    }
  ],
  "foo": "bar"
}
lambshaanxy
źródło
32
Sprytne! Włożyłem to do mojego ~ / .irbrc: def json_pp (json) stawia JSON.pretty_generate (JSON.parse (json)) koniec
TheDeadSerious
10
Aby uczynić to użytecznym w Railsach, wydaje się, że powinieneś udzielić odpowiedzi zawierającej kod, który może być użyty w tym samym kontekście coformat.json { render :json => @whatever }
iconoclast
9
Z pewnością ładne drukowanie powinno być używane tylko do debugowania po stronie serwera? Jeśli umieścisz powyższy kod w kontrolerze, będziesz mieć mnóstwo niepotrzebnych białych znaków we wszystkich odpowiedziach, co nie jest nawet potrzebne do debugowania po stronie klienta, ponieważ wszelkie narzędzia warte swojej soli (np. Firebug) już obsługują ładny druk JSON.
lambshaanxy
8
@jpatokal: możesz rozważyć inne lepsze opcje, ale pytanie brzmiało, jak to zrobić w Railsach. Mówienie „nie chcesz tego robić w Railsach” jest odpowiedzią. Oczywiście wiele osób chce to zrobić w Railsach.
iconoclast
39
Oryginalny plakat nie mówił nic o tym, gdzie w aplikacji Railsowej chce tego użyć, więc odpowiedziałem linią Ruby, która będzie działać wszędzie. Aby go użyć do wygenerowania odpowiedzi JSON w Rails kontroler , już odpowiedział na własne pytanie: format.json { render :json => JSON.pretty_generate(my_json) }.
lambshaanxy
78

Dzięki Rack Middleware i Rails 3 możesz wypisać ładny JSON dla każdego żądania bez zmiany kontrolera swojej aplikacji. Napisałem taki fragment oprogramowania pośredniego i ładnie wydrukowałem JSON w przeglądarce i curlwynikach.

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

Powyższy kod należy umieścić w app/middleware/pretty_json_response.rbprojekcie Rails. Ostatnim krokiem jest zarejestrowanie oprogramowania pośredniego w config/environments/development.rb:

config.middleware.use PrettyJsonResponse

Nie polecam go używać wproduction.rb . Ponowna analiza JSON może obniżyć czas odpowiedzi i przepustowość aplikacji produkcyjnej. W końcu może zostać wprowadzona dodatkowa logika, taka jak nagłówek „X-Pretty-Json: true”, aby wyzwalać formatowanie ręcznych żądań zwijania na żądanie.

(Testowane z Railsami 3.2.8-5.0.0, Ruby 1.9.3-2.2.0, Linux)

gertas
źródło
2
Jak radzisz sobie z redefinicją funkcji to_json przez ActiveSupport? To powstrzymuje mnie przed ładnym drukowaniem, gdy ActiveSupport jest obecny.
Ammo Goettsch
1
Naprawdę mnie to nie obchodzi, to_json, as_json, jbuilder, którego używam głównie - cokolwiek, oprogramowanie pośrednie przekształca każde wyjście JSON. W miarę możliwości staram się unikać otwierania zajęć.
gertas
1
Musiałem zmienić linię parsowania, obj = JSON.parse(response.body.first)aby działała.
Kimmo Lehto
5
Działa również świetnie w Rails 4 ... dzięki! Wolę to od metod bardziej specyficznych dla biblioteki (jak w zaakceptowanej odpowiedzi). Ponieważ i tak powinieneś używać tego tylko w trybie deweloperskim, hit wydajności nie jest wielkim problemem.
elsurudo
3
W Rails 5 musiałem zmienić Rack::Utils.bytesize(pretty_str).to_ssię pretty_str.bytesize.to_si działa świetnie!
panteo
77

<pre>Tag HTML, stosować JSON.pretty_generate, będzie świadczyć JSON dość w widoku. Byłem bardzo szczęśliwy, kiedy mój znakomity szef pokazał mi to:

<% if @data.present? %>
   <pre><%= JSON.pretty_generate(@data) %></pre>
<% end %>
Roger Garza
źródło
5
Tak czysty i zwięzły!
Sean Szurko
23

Jeśli chcesz:

  1. Prettify automatycznie wszystkie wychodzące odpowiedzi JSON z Twojej aplikacji.
  2. Unikaj zanieczyszczenia obiektu # to_json / # as_json
  3. Unikaj analizowania / ponownego renderowania JSON przy użyciu oprogramowania pośredniego (FUCK!)
  4. Zrób to na KOLEJOWO

Następnie ... zamień ActionController :: Renderer dla JSON! Wystarczy dodać następujący kod do 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
źródło
To jest niesamowite, ale w rzeczywistości powoduje, że daty / godziny są renderowane inaczej: gist.github.com/nornagon/9c24b68bd6d3e871add3
nornagon
Kilka problemów z tym: (1) JSON.pretty_generate wymaga json.respond_to?(:to_h)lub :to_hash. (2) pretty_generate może zadławić się rzeczami, których nie robi to_json.
Christopher Oezbek,
@nornagon Nie zastosowałem tej zmiany i dostaję tę samą różnicę, którą widziałeś między .to_json i pretty_generate. Widzę to tylko w konsoli szyn, a nie w zwykłym irb. Myślę, że może to być ogólna rzecz związana z szynami, nie ma nic wspólnego z tą łatką. Ponadto Time.parse zwraca ten sam wynik po konwersji łańcucha z powrotem na czas dla obu formatów. Byłoby to tylko niewielką niedogodnością podczas wyszukiwania dzienników w poszukiwaniu znaczników czasu, ale jeśli tak czy inaczej, dodawanie kilku znaków + nie jest tak naprawdę wielkim problemem.
con--
@nornagon wygląda na problem, który widziałeś, to redefinicja to_json przez ActiveSupport, jak wspomniano w komentarzu Ammo Goettsch
con--
17

Sprawdź Awesome Print . Parsuj ciąg JSON w Ruby Hash, a następnie wyświetl go w następujący apsposób:

require "awesome_print"
require "json"

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

ap(JSON.parse(json))

Dzięki powyższym zobaczysz:

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

Niesamowity wydruk doda również trochę koloru, którego nie pokaże przepełnienie stosu.

Synthead
źródło
2
Zgadzam się z Tobą! awesome_print jest po prostu niesamowity!
Aashish
2
Używamy awesome_print również do naszych projektów i działa to tak, jakby nazwa -> awesome
Simon Franzen
13

Zrzucanie obiektu ActiveRecord do JSON (w konsoli Rails):

pp User.first.as_json

# => {
 "id" => 1,
 "first_name" => "Polar",
 "last_name" => "Bear"
}
Thomas Klemm
źródło
3
aby uzyskać ciąg ppzamiast drukowania na standardowe wyjście, użyj User.first.as_json.pretty_inspect. Działa dla mnie dobrze.
Johnny Wong,
12

Korzystanie z <pre>kodu HTML i pretty_generatejest dobrą sztuczką:

<%
  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
źródło
12

Jeśli okaże się, że pretty_generateopcja wbudowana w bibliotekę JSON Ruby nie jest wystarczająco „ładna”, polecam własny klejnot NeatJSON do formatowania.

Aby go użyć:

gem install neatjson

a następnie użyj

JSON.neat_generate

zamiast

JSON.pretty_generate

Podobnie jak Ruby, pputrzyma obiekty i tablice w jednej linii, gdy się zmieszczą, ale w razie potrzeby zawinie do wielu. Na przykład:

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

Obsługuje także różne opcje formatowania, aby dodatkowo dostosować wyniki. Na przykład, ile spacji przed / po dwukropkach? Przed / po przecinkach? W nawiasach tablic i obiektów? Czy chcesz posortować klucze swojego obiektu? Czy chcesz, aby wszystkie dwukropki były ustawione w jednej linii?

Phrogz
źródło
2
Ten klejnot kołysze - wyrównanie na okrężnicy jest szczególnie słodkie!
webdevguy,
8

Oto rozwiązanie oprogramowania pośredniego zmodyfikowane z tej doskonałej odpowiedzi przez @gertas . To rozwiązanie nie jest specyficzne dla Railsów - powinno działać z dowolną aplikacją Rack.

Technika oprogramowania pośredniego zastosowana tutaj, przy użyciu #each, została wyjaśniona w ASCIIcasts 151: Rack Middleware autorstwa Eifion Bedford.

Ten kod znajduje się w 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

Aby go włączyć, dodaj to do config / environment / test.rb i config / environment / development.rb:

config.middleware.use "PrettyJsonResponse"

Ponieważ @gertas ostrzega w swojej wersji tego rozwiązania, unikaj używania go w środowisku produkcyjnym. Jest trochę powolny.

Testowane z Rails 4.1.6.

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

Oto moje rozwiązanie, które zaczerpnąłem z innych postów podczas mojego wyszukiwania.

Pozwala to na przesłanie danych wyjściowych pp i jj do pliku w razie potrzeby.

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
źródło
3

Użyłem klejnotu CodeRay i działa całkiem dobrze. Format zawiera kolory i rozpoznaje wiele różnych formatów.

Użyłem go jako klejnotu, którego można używać do debugowania interfejsów API szyn i działa całkiem dobrze.

Nawiasem mówiąc, klejnot nazywa się „api_explorer” ( http://www.github.com/toptierlabs/api_explorer )

Tony
źródło
3

Jeśli chcesz szybko zaimplementować to w akcji kontrolera Rails, aby wysłać odpowiedź JSON:

def index
  my_json = '{ "key": "value" }'
  render json: JSON.pretty_generate( JSON.parse my_json )
end
sealocal
źródło
2

Jeśli używasz RABL , możesz go skonfigurować zgodnie z opisem tutaj, aby użyć 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

Problem z używaniem JSON.pretty_generate polega na tym, że walidatory schematu JSON nie będą już zadowolone z ciągów daty i godziny. Możesz to naprawić w pliku config / initializers / rabl_config.rb za pomocą:

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
źródło
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
źródło
1

Korzystam z następujących, ponieważ uważam, że nagłówki, status i dane wyjściowe JSON są przydatne jako zestaw. Procedura połączeń została opracowana na zalecenie z prezentacji w formie railscastów pod adresem : 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
źródło
1

Ładny wariant wydruku:

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

Wynik:

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

Najprostszy przykład, mógłbym wymyślić:

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

Przykład konsoli 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
źródło