← Back to blog

How to Pretty Print a Hash in Ruby (pp, pretty_generate, and awesome_print)

· Lachlan Young

At some point every Ruby developer types puts my_hash into a console, gets back a single line of {"user"=>{"id"=>...}} that wraps four times in the terminal, and squints at it trying to find the one field they actually care about. The bigger the hash, the worse it gets. A serialized API response or a set of ActiveRecord attributes can run to hundreds of keys, and the default output crams all of it onto one line with no indentation and no structure.

The good news is that Ruby ships with several ways to print a hash so a human can read it, and there are a couple of gems worth knowing when the built-ins are not enough. Here is the practical rundown, with the tradeoffs, so you reach for the right one instead of always defaulting to puts.

The default is the problem

Start with what you get for free, so the improvements make sense:

hash = { user: { id: 42, name: "Ada", roles: [:admin, :editor], active: true }, meta: { created_at: "2026-07-27", source: "import" } }

puts hash
# {:user=>{:id=>42, :name=>"Ada", :roles=>[:admin, :editor], :active=>true}, :meta=>{:created_at=>"2026-07-27", :source=>"import"}}

puts calls to_s, which for a hash is the same as inspect. It is compact, which is fine for a five-key hash and useless for a fifty-key one. There are no line breaks, so nesting is invisible and your terminal does the wrapping at whatever width it happens to be. This is the output you are trying to escape.

pp: the built-in you should use first

Ruby’s standard library includes pp, short for pretty print. It is available everywhere without any require in modern Ruby, and it indents nested structures onto multiple lines:

pp hash
# {:user=>{:id=>42, :name=>"Ada", :roles=>[:admin, :editor], :active=>true},
#  :meta=>{:created_at=>"2026-07-27", :source=>"import"}}

For a hash this small the win is modest, but pp scales. Once a structure is wide enough that it would overflow the line, pp breaks it across lines and aligns the nesting, so you can actually see which keys sit inside which. It also returns its argument, which matters more than it sounds: you can drop pp into the middle of a method or a chain without changing behavior.

def build_payload(order)
  pp order.attributes   # prints, and returns attributes untouched
  serialize(order)
end

If you only remember one thing from this post, make it pp instead of puts for anything structured. It costs one extra character and gives you readable output by default.

JSON.pretty_generate: when you want valid JSON

pp prints Ruby syntax, with hashrockets, symbols, and nil. Sometimes what you actually want is JSON, either because you are debugging an API and want to compare against the real response, or because you want to paste the output somewhere that expects JSON. That is what JSON.pretty_generate is for:

require "json"

puts JSON.pretty_generate(hash)
# {
#   "user": {
#     "id": 42,
#     "name": "Ada",
#     "roles": [
#       "admin",
#       "editor"
#     ],
#     "active": true
#   },
#   "meta": {
#     "created_at": "2026-07-27",
#     "source": "import"
#   }
# }

Note what happened to the data on the way out. Symbol keys became strings, and the :admin and :editor symbols became plain strings too, because JSON has no concept of a symbol. That is exactly what you want when comparing against an HTTP response, and exactly what you do not want if you are trying to see the true Ruby types in the hash. Pick pp when types matter, and pretty_generate when JSON shape matters.

One habit worth building in Rails: when a request spec fails on the response body, print JSON.pretty_generate(JSON.parse(response.body)) rather than the raw string. The raw body is a single escaped line, and the parsed and reprinted version is something you can actually read and diff.

awesome_print and amazing_print: color and alignment

When you spend a lot of time in a console, the gem worth installing is amazing_print, the maintained successor to the long-popular awesome_print. It adds indentation, alignment, and syntax coloring, which makes large hashes genuinely scannable:

require "amazing_print"

ap hash

In a real terminal that prints with each key on its own line, the keys aligned, and colors distinguishing symbols, strings, numbers, and booleans. The color is the part you cannot get from pp, and for a hash with mixed types it is the difference between reading and hunting. If you use Rails, add it to the :development and :test groups and configure it in your .irbrc or console initializer so ap is always there.

The tradeoff is that it is a dependency and its output is not valid Ruby or JSON, so it is for reading, not for copying into code. Reach for it in the console, not in code you commit.

Printing inside a Rails app

A few Rails-specific notes, because the objects you print are rarely plain hashes.

ActiveRecord models do not print usefully by default, so call attributes first to get the underlying hash:

pp user.attributes

Log output is its own case. In a Rails logger, multi-line pretty printing can be awkward because each line becomes its own log entry with its own timestamp. For logs, a single-line hash.inspect or a structured JSON log is usually easier to grep than pretty-printed output split across twenty lines. Save the pretty printing for consoles and test failures, where a human is reading interactively.

When the real problem is comparison, not printing

Here is the honest limit of all of this. Pretty printing makes one hash readable. It does nothing for the actual hardest case, which is when you have two large hashes that are supposed to be identical, a test says they are not, and you need to find the single key that differs. Printing both of them prettily just gives you two long readable blocks that you now have to compare line by line, and your eyes are no better at that than they were before.

That is a genuinely different task. Printing is a formatting problem; comparison is a diffing problem. For a quick check in code you can compute the difference yourself:

def hash_diff(a, b)
  keys = a.keys | b.keys
  keys.each_with_object({}) do |key, diff|
    diff[key] = [a[key], b[key]] if a[key] != b[key]
  end
end

pp hash_diff(expected, actual)

That collapses two big hashes down to only the keys that disagree, which is usually what you were after. It is shallow, though, so for deeply nested structures it will report a whole nested hash as changed when only one leaf differs.

When you have two hashes that should match but do not, paste them into RubyHash for a clean side-by-side diff that sorts the keys and highlights exactly which value changed, right down through the nesting, all in your browser.

Enjoyed this post?

Subscribe to get notified when we publish more Ruby and Rails content.