วิธีจัดรูปแบบเอาต์พุต JSON แบบ "สวย" ใน Ruby on Rails


626

ฉันต้องการให้เอาต์พุต JSON ของฉันใน Ruby on Rails เป็น "สวย" หรือจัดรูปแบบไว้อย่างดี

ตอนนี้ฉันโทรto_jsonแล้ว JSON ของฉันทั้งหมดอยู่ในบรรทัดเดียว ในบางครั้งอาจเป็นเรื่องยากที่จะดูว่ามีปัญหาในสตรีมเอาต์พุต JSON

มีวิธีการกำหนดค่าให้ JSON ของฉัน "สวย" หรือจัดรูปแบบใน Rails หรือไม่?


2
ไม่แน่ใจว่าคุณกำลังดูอยู่ที่ไหน แต่ในคอนโซลของ webkit มันสร้างแผนผังที่ดีจากการบันทึกหรือร้องขอ JSON
Ryan Florence

8
สิ่งหนึ่งที่ต้องจำเมื่อทำสิ่งนี้คือขนาดของเนื้อหา JSON ของคุณจะขึ้นบอลลูนเนื่องจากช่องว่างเพิ่มเติม ในสภาพแวดล้อมการพัฒนามักจะมีประโยชน์ที่จะให้ JSON อ่านง่าย แต่ในสภาพแวดล้อมการใช้งานจริงคุณต้องการให้เนื้อหาของคุณเป็นแบบลีนเพราะคุณสามารถรับความเร็วและการตอบสนองในเบราว์เซอร์ของผู้ใช้ได้
Tin Man

2
ใช้สิ่งที่y my_jsonจะจัดรูปแบบถ้าคุณต้องการแก้ไขอย่างรวดเร็ว
randomor

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

yมีอยู่ในคอนโซลทางรถไฟ
โซเฟียเฟิง

คำตอบ:


999

ใช้pretty_generate()ฟังก์ชันสร้างขึ้นใน JSON เวอร์ชันใหม่กว่า ตัวอย่างเช่น:

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

สิ่งที่ทำให้คุณได้รับ:

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

32
Nifty! ฉันได้ใส่สิ่งนี้ลงใน ~ / .irbrc: def json_pp (json) ทำให้ JSON.pretty_generate (JSON.parse (json)) สิ้นสุด
TheDeadSerious

10
เพื่อให้เป็นประโยชน์ใน Rails ดูเหมือนว่าคุณควรให้คำตอบซึ่งรวมถึงรหัสที่สามารถใช้ในบริบทเดียวกับformat.json { render :json => @whatever }
iconoclast

9
ควรใช้การพริ้นติ้งสวย ๆ สำหรับการดีบักฝั่งเซิร์ฟเวอร์หรือไม่ หากคุณติดรหัสข้างบนในคอนโทรลเลอร์คุณจะมีช่องว่างที่ไร้ประโยชน์มากมายในการตอบกลับทั้งหมดซึ่งไม่จำเป็นสำหรับการดีบักฝั่งไคลเอ็นต์เนื่องจากเครื่องมือใด ๆ ที่คุ้มค่าเกลือของพวกเขา (เช่น Firebug) จัดการ JSON ที่น่ารักแล้ว
lambshaanxy

8
@jpatokal: คุณอาจคิดว่ามีตัวเลือกอื่นที่ดีกว่านี้ แต่คำถามก็คือทำอย่างไรถึงจะใช้งาน Rails ได้ การพูดว่า "คุณไม่ต้องการทำเช่นนั้นใน Rails" เป็นคำตอบที่ไม่ใช่ เห็นได้ชัดว่าผู้คนจำนวนมากต้องการที่จะทำมันใน Rails
iconoclast

39
โปสเตอร์เดิมกล่าวว่าไม่มีอะไรเกี่ยวกับที่อยู่ในแอพพลิเค Rails เขาต้องการที่จะใช้นี้ดังนั้นฉันตอบกับสายของทับทิมที่จะทำงานที่ใดก็ได้ ที่จะใช้ในการสร้างการตอบสนอง JSON ใน Rails ควบคุมformat.json { render :json => JSON.pretty_generate(my_json) }คุณตอบแล้วคำถามของคุณเอง:
lambshaanxy

78

ขอบคุณ Rack Middleware และ Rails 3 คุณสามารถส่งออก JSON ที่น่ารักได้ทุกคำขอโดยไม่ต้องเปลี่ยนคอนโทรลเลอร์ของแอพของคุณ ฉันได้เขียนตัวอย่างมิดเดิลแวร์ดังกล่าวและฉันได้รับการพิมพ์ JSON อย่างดีในเบราว์เซอร์และ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

ควรใส่โค้ดด้านบนในapp/middleware/pretty_json_response.rbโปรเจค Rails ของคุณ และขั้นตอนสุดท้ายคือการลงทะเบียนมิดเดิลแวร์ในconfig/environments/development.rb:

config.middleware.use PrettyJsonResponse

production.rbผมไม่แนะนำให้ใช้ใน การแยกวิเคราะห์ JSON อาจลดเวลาตอบสนองและปริมาณงานของแอปที่ใช้งานจริงของคุณ ตรรกะเพิ่มเติมในท้ายที่สุดเช่นส่วนหัว 'X-Pretty-Json: จริง' อาจถูกนำมาใช้เพื่อทริกเกอร์การจัดรูปแบบสำหรับการร้องขอ curl ด้วยตนเองตามความต้องการ

(ทดสอบกับ Rails 3.2.8-5.0.0, Ruby 1.9.3-2.2.0, Linux)


2
คุณรู้จักการกำหนด to_json ของActiveSupport อีกครั้งได้อย่างไร สิ่งนี้ทำให้ฉันไม่สวยเมื่อกำลังใช้งาน ActiveSupport
กระสุน Goettsch

1
ฉันไม่สนใจจริงๆ to_json, as_json, jbuilderซึ่งฉันใช้เป็นส่วนใหญ่ - อะไรก็ตามมิดเดิลแวร์จะแปลงเอาต์พุต JSON ใด ๆ ฉันพยายามหลีกเลี่ยงการเปิดชั้นเรียนทุกครั้งที่ทำได้
gertas

1
ฉันต้องเปลี่ยนบรรทัดแยกวิเคราะห์obj = JSON.parse(response.body.first)เพื่อให้ทำงานได้
Kimmo Lehto

5
ใช้งานได้ดีใน Rails 4 เช่นกัน ... ขอบคุณ! ฉันชอบวิธีนี้เฉพาะห้องสมุดมากขึ้น (ตามคำตอบที่ยอมรับ) เนื่องจากคุณควรใช้สิ่งนี้ในโหมด dev เท่านั้นการได้รับประสิทธิภาพไม่ใช่เรื่องใหญ่
elsurudo

3
ในทางรถไฟ 5 ผมต้องเปลี่ยนRack::Utils.bytesize(pretty_str).to_sไปpretty_str.bytesize.to_sและการทำงานที่ดี!
panteo

77

<pre>แท็กใน HTML, ใช้กับJSON.pretty_generateจะทำให้ JSON สวยในมุมมองของคุณ ฉันมีความสุขมากเมื่อเจ้านายที่มีชื่อเสียงของฉันแสดงสิ่งนี้ให้ฉัน:

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

5
สะอาดและรัดกุม!
Sean Szurko

23

ถ้าคุณต้องการ:

  1. ระบุการตอบสนอง JSON ขาออกทั้งหมดจากแอปของคุณโดยอัตโนมัติ
  2. หลีกเลี่ยงการสร้างมลภาวะ Object # to_json / # as_json
  3. หลีกเลี่ยงการแยกวิเคราะห์ / แสดงผล JSON อีกครั้งโดยใช้มิดเดิลแวร์ (YUCK!)
  4. ทำทางรถไฟ!

จากนั้น ... แทนที่ ActionController :: Renderer สำหรับ JSON! เพียงเพิ่มรหัสต่อไปนี้ใน 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

สิ่งนี้ยอดเยี่ยม แต่จริง ๆ แล้วทำให้วันที่ / เวลาแสดงผลแตกต่างกัน: gist.github.com/nornagon/9c24b68bd6d3e871add3
nornagon

หลายปัญหากับเรื่องนี้ (1) JSON.pretty_generate ต้องหรือjson.respond_to?(:to_h) :to_hash(2) pretty_generate สามารถทำให้หายใจไม่ออกในสิ่งที่ to_json ไม่ได้
Christopher Oezbek

@nornagon ฉันไม่ได้ใช้การเปลี่ยนแปลงนี้และฉันได้รับความแตกต่างแบบเดียวกันกับที่คุณเห็นระหว่าง. to_json และ pretty_generate ฉันเห็นมันในคอนโซลของรางเท่านั้นไม่ใช่ irb ธรรมดา ฉันคิดว่านี่อาจเป็นเรื่องของรางทั่วไปไม่มีอะไรเกี่ยวข้องกับ patch นี้ นอกจากนี้ Time.parse จะส่งคืนผลลัพธ์เดียวกันเมื่อคุณแปลงสตริงกลับเป็นเวลาสำหรับทั้งสองรูปแบบ มันอาจจะเป็นความไม่สะดวกเล็ก ๆ น้อย ๆ เมื่อค้นหาบันทึกการประทับเวลา แต่ถ้าคุณกำลังเพิ่ม grepping อยู่ดีการเพิ่ม \ s + สักเล็กน้อยนั้นไม่ใช่เรื่องใหญ่
con--

@nornagon ดูเหมือนว่าปัญหาที่คุณเห็นคือการกำหนดนิยามใหม่ของ to_json ของActiveSupport ดังที่ได้กล่าวไว้ในความคิดเห็นของ Ammo Goettsch
con

17

ตรวจสอบการพิมพ์ที่ยอดเยี่ยม แยกสตริง JSON เป็น Ruby Hash จากนั้นแสดงด้วยapดังนี้:

require "awesome_print"
require "json"

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

ap(JSON.parse(json))

ด้วยข้างต้นคุณจะเห็น:

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

Awesome Print จะเพิ่มสีที่ Stack Overflow จะไม่แสดงให้คุณเห็นด้วย


2
เห็นด้วยกับคุณ! awesome_print ง่ายมาก!
Aashish

2
เรากำลังใช้ Awesome_print สำหรับโครงการของเราด้วยและใช้งานได้เหมือนชื่อ -> ยอดเยี่ยม
Simon Franzen

13

การดัมพ์อ็อบเจ็กต์ ActiveRecord ไปยัง JSON (ในคอนโซล Rails):

pp User.first.as_json

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

3
ที่จะได้รับจากสตริงแทนการพิมพ์ออกมาตรฐานการใช้งานpp User.first.as_json.pretty_inspectทำงานได้ดีสำหรับฉัน
Johnny Wong

12

ใช้<pre>โค้ด HTML และpretty_generateเป็นเคล็ดลับที่ดี:

<%
  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

หากคุณพบว่าpretty_generateตัวเลือกที่มีอยู่ในไลบรารี JSON ของรูบี้ไม่ได้ "สวย" พอฉันแนะนำอัญมณีNeatJSONของฉันเองสำหรับการจัดรูปแบบของคุณ

วิธีใช้:

gem install neatjson

จากนั้นใช้

JSON.neat_generate

แทน

JSON.pretty_generate

เช่นเดียวกับ Ruby ppมันจะเก็บวัตถุและอาร์เรย์ในหนึ่งบรรทัดเมื่อมันพอดี แต่ห่อหลายอันตามต้องการ ตัวอย่างเช่น:

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

นอกจากนี้ยังสนับสนุนตัวเลือกการจัดรูปแบบที่หลากหลายเพื่อปรับแต่งผลลัพธ์ของคุณเพิ่มเติม ตัวอย่างเช่นมีช่องว่างกี่ช่องก่อน / หลังโคลอน ก่อน / หลังเครื่องหมายจุลภาค? ข้างในวงเล็บของอาร์เรย์และวัตถุ? คุณต้องการเรียงลำดับคีย์ของวัตถุของคุณหรือไม่? คุณต้องการให้ colons เรียงกันหมดไหม?


2
อัญมณีหินก้อนนี้ - แนวของโคลอนนั้นหวานเป็นพิเศษ!
webdevguy

8

นี่เป็นวิธีการแก้ตัวกลางดัดแปลงมาจากคำตอบที่ดีเยี่ยมนี้โดย @gertas โซลูชันนี้ไม่ได้ระบุเฉพาะกับ Rails - ควรทำงานกับแอปพลิเคชัน Rack ใด ๆ

เทคนิคมิดเดิลแวร์ที่ใช้ที่นี่โดยใช้ #each ได้รับการอธิบายที่ASCIIcasts 151: Rack Middlewareโดย Eifion Bedford

รหัสนี้อยู่ในแอพ / มิดเดิลแวร์ / 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

หากต้องการเปิดใช้งานให้เพิ่มสิ่งนี้ลงใน config / environment / test.rb และ config / environment / development.rb:

config.middleware.use "PrettyJsonResponse"

ตามที่ @ gertas เตือนในเวอร์ชันของโซลูชันนี้ให้หลีกเลี่ยงการใช้ในการผลิต มันค่อนข้างช้า

ทดสอบกับ Rails 4.1.6



4

นี่คือทางออกของฉันที่ฉันได้รับจากโพสต์อื่น ๆ ในระหว่างการค้นหา

สิ่งนี้อนุญาตให้คุณส่งเอาต์พุต pp และ jj ไปยังไฟล์ตามต้องการ

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

ฉันใช้พลอย CodeRay และมันใช้งานได้ดี รูปแบบมีสีและรู้จักรูปแบบต่าง ๆ มากมาย

ฉันใช้มันกับอัญมณีที่สามารถใช้สำหรับการดีบัก APIs ทางรถไฟและใช้งานได้ดี

โดยวิธีการที่อัญมณีมีชื่อว่า 'api_explorer' ( http://www.github.com/toptierlabs/api_explorer )


3

หากคุณต้องการใช้สิ่งนี้อย่างรวดเร็วในแอคชั่นควบคุม Rails เพื่อส่งการตอบสนอง JSON:

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

2

หากคุณใช้RABLคุณสามารถกำหนดค่าตามที่อธิบายไว้ที่นี่เพื่อใช้ 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

ปัญหาเกี่ยวกับการใช้ JSON.pretty_generate คือตัวตรวจสอบ JSON schema จะไม่พอใจกับสตริง datetime ของคุณอีกต่อไป คุณสามารถแก้ไขได้ใน config / initializers / rabl_config.rb ด้วย:

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

ฉันใช้ต่อไปนี้ในขณะที่ฉันค้นหาส่วนหัวสถานะและเอาต์พุต JSON มีประโยชน์เป็นชุด รูทีนการโทรแตกออกตามคำแนะนำจากการนำเสนอทางรถไฟที่: 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

ตัวแปรการพิมพ์ที่น่ารัก:

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

ผลลัพธ์:

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

0

ตัวอย่างที่ง่ายที่สุดฉันนึกถึง:

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

ตัวอย่างคอนโซล 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.