Parse JSON in Ruby

Ruby ships with a built-in JSON gem in its standard library since Ruby 1.9.3 (2011) — no third-party package required. A single require 'json' gives you JSON.parse to turn a JSON string into a Ruby Hash or Array, and JSON.generate (or .to_json) to serialize objects back. This guide covers every option you'll actually use in production, including symbolize_names, file reading, error handling, and Rails-specific patterns.

Validate your JSON in Jsonic before parsing it in Ruby.

Open JSON Formatter

Quick start: require 'json' and JSON.parse

JSON parsing in Ruby requires no gem install. Add require 'json' and call JSON.parse with any valid JSON string. The return value mirrors the JSON type: a JSON object becomes a Ruby Hash, a JSON array becomes a Ruby Array, strings become String, numbers become Integer or Float, booleans become true/ false, and null becomes nil.

require 'json'

# Parse a JSON object → Ruby Hash
json = '{"name":"Alice","age":30,"active":true}'
data = JSON.parse(json)

puts data.class        # Hash
puts data["name"]      # Alice
puts data["age"]       # 30
puts data["active"]    # true

# Parse a JSON array → Ruby Array
array_json = '[1, 2, 3, "four", null]'
items = JSON.parse(array_json)

puts items.class       # Array
puts items[0]          # 1
puts items[4].nil?     # true  (JSON null → Ruby nil)

# Nested objects are parsed recursively
nested = '{"user":{"id":1,"roles":["admin","editor"]}}'
result = JSON.parse(nested)
puts result["user"]["roles"][0]  # admin

Unlike PHP's json_decode which returns a stdClass by default, Ruby's JSON.parse always returns a plain Hash for JSON objects — no extra options needed to get dictionary-style key access.

Parsing to Hash vs symbolized keys (symbolize_names)

By default, JSON.parse returns a Hash with string keys. Pass symbolize_names: true to convert every string key to a Ruby symbol instead. Symbol lookup in a Ruby hash is 20–30% faster than string lookup, and symbols read more naturally in Ruby code. The conversion is recursive — keys in nested hashes are also symbolized.

require 'json'

json = '{"user":{"name":"Bob","city":"Tokyo"}}'

# Default: string keys
string_hash = JSON.parse(json)
puts string_hash["user"]["name"]   # Bob
puts string_hash[:user]            # nil — symbol key not found

# With symbolize_names: true — all keys become symbols
sym_hash = JSON.parse(json, symbolize_names: true)
puts sym_hash[:user][:name]        # Bob
puts sym_hash[:user][:city]        # Tokyo

# symbolize_names works recursively on nested hashes
complex = '{"order":{"id":99,"items":[{"sku":"A1","qty":2}]}}'
data = JSON.parse(complex, symbolize_names: true)
puts data[:order][:items][0][:sku] # A1

Choose string keys when you're working with external APIs whose key names you don't control and may contain characters that are awkward as symbols. Use symbolize_names: true for internal data structures where you want the performance benefit and the cleaner data[:key] syntax.

JSON.parse vs JSON.parse! — when to use each

Ruby provides two parse methods with subtly different security behavior. Understanding the difference matters when accepting JSON from untrusted sources.

MethodSecurity checksUse caseAlias of
JSON.parseFull — safe for untrusted inputAPI responses, user input, external files
JSON.parse!Reduced — omits some object creation guardsTrusted internal data onlyJSON.load
JSON.loadReduced — same as JSON.parse!Trusted internal data onlyJSON.parse!
require 'json'

# JSON.parse — safe, recommended for all external input
trusted_or_not = '{"key":"value"}'
data = JSON.parse(trusted_or_not)

# JSON.parse! — skips some security checks
# Only use when you fully control and trust the JSON source
internal_data = '{"config":"value"}'
data = JSON.parse!(internal_data)

# JSON.load is an alias of JSON.parse! — equally unsafe for untrusted input
# Avoid using JSON.load unless you know exactly what you're doing
data = JSON.load(internal_data)  # same as JSON.parse!

The practical rule: always use JSON.parse. Reserve JSON.parse! or JSON.load for cases where you serialized the JSON yourself and immediately parse it back in the same trusted context.

Generate JSON with JSON.generate and .to_json

Serializing Ruby objects to JSON is equally simple. The JSON gem adds a .to_json method to all core Ruby objects, and JSON.generate is the module-level equivalent. Both produce identical compact output with no whitespace.

require 'json'

user = { name: "Alice", age: 30, roles: ["admin", "editor"] }

# Both produce identical output
puts JSON.generate(user)
# {"name":"Alice","age":30,"roles":["admin","editor"]}

puts user.to_json
# {"name":"Alice","age":30,"roles":["admin","editor"]}

# Works on Arrays too
puts [1, 2, 3].to_json      # [1,2,3]
puts JSON.generate([1, 2, 3]) # [1,2,3]

# Nested structures are serialized recursively
order = {
  id: 101,
  items: [
    { sku: "A1", price: 9.99 },
    { sku: "B2", price: 14.50 }
  ],
  shipped: false,
  note: nil
}

puts JSON.generate(order)
# {"id":101,"items":[{"sku":"A1","price":9.99},{"sku":"B2","price":14.5}],"shipped":false,"note":null}

Note that Ruby nil serializes to JSON null, Ruby false/true map to JSON booleans, and Ruby symbols used as values (not keys) are serialized as strings. Symbol keys in a Hash are also output as JSON string keys.

Pretty-print JSON in Ruby

JSON.generate and .to_json produce compact single-line output. Use JSON.pretty_generate when you need human-readable, indented JSON — for log files, config output, or debugging.

require 'json'

data = {
  name: "Alice",
  scores: [95, 87, 92],
  address: { city: "Tokyo", country: "Japan" }
}

# Compact (default)
puts JSON.generate(data)
# {"name":"Alice","scores":[95,87,92],"address":{"city":"Tokyo","country":"Japan"}}

# Pretty-printed — 2-space indentation and newlines
puts JSON.pretty_generate(data)
# {
#   "name": "Alice",
#   "scores": [
#     95,
#     87,
#     92
#   ],
#   "address": {
#     "city": "Tokyo",
#     "country": "Japan"
#   }
# }

# Write pretty JSON to a file
File.write('output.json', JSON.pretty_generate(data))

JSON.pretty_generate uses 2-space indentation by default. If you need custom formatting, use JSON.generate with the indent, object_nl, and array_nl options. For most use cases, pretty_generate is the right choice.

Read a JSON file with JSON.load_file

Reading a JSON configuration or data file is a very common task. Since Ruby 2.0, the JSON gem provides JSON.load_file to open, read, and parse a file in a single call — no need to manually open a file handle.

require 'json'

# config.json:
# {
#   "database": { "host": "localhost", "port": 5432 },
#   "debug": false
# }

# One-liner: open, read, and parse
config = JSON.load_file('config.json')
puts config["database"]["host"]  # localhost
puts config["database"]["port"]  # 5432
puts config["debug"]             # false

# With symbolized keys (symbolize_names supported since Ruby 3.0)
config = JSON.load_file('config.json', symbolize_names: true)
puts config[:database][:port]    # 5432

# For untrusted files, prefer File.read + JSON.parse for full security checks:
config = JSON.parse(File.read('config.json'))

# Write a Ruby object to a JSON file
settings = { theme: "dark", language: "en" }
File.write('settings.json', JSON.pretty_generate(settings))

For files you control (application configs, test fixtures, seed data), JSON.load_file is the most concise option. For files received from external sources, use JSON.parse(File.read(path)) to go through the full security checks of JSON.parse.

Error handling: JSON::ParserError

When JSON.parse receives a string that is not valid JSON, it raises a JSON::ParserError. This happens with truncated API responses, HTML error pages returned instead of JSON, empty strings, or data with unescaped characters. Always rescue this exception when parsing external input.

require 'json'

def safe_parse(raw)
  JSON.parse(raw)
rescue JSON::ParserError => e
  puts "Invalid JSON: #{e.message}"
  nil
end

# Invalid JSON — raises JSON::ParserError
safe_parse("{bad json}")        # Invalid JSON: unexpected token...
safe_parse("")                  # Invalid JSON: unexpected token...
safe_parse("undefined")         # Invalid JSON: unexpected token...
safe_parse("<html>error</html>") # Invalid JSON: unexpected token...

# Valid JSON — returns parsed value
safe_parse('{"ok":true}')       # => {"ok" => true}
safe_parse("null")              # => nil (valid JSON!)
safe_parse("42")                # => 42  (valid JSON!)

# Practical pattern: parse an HTTP response body safely
require 'net/http'

def fetch_json(url)
  response = Net::HTTP.get(URI(url))
  JSON.parse(response)
rescue JSON::ParserError => e
  warn "Could not parse response as JSON: #{e.message}"
  nil
rescue => e
  warn "Request failed: #{e.message}"
  nil
end

Note that "null" and bare numbers like "42" are valid JSON and parse without error — nil return from safe_parse does not necessarily mean an error occurred. Check the return value type when the input might be a bare JSON value rather than an object or array.

Rails-specific: parsing API responses and strong parameters

Rails applications interact with JSON constantly — incoming webhook payloads, external API responses, and request bodies from JSON clients. Rails adds several conveniences on top of the standard JSON gem.

# Rails automatically parses incoming JSON request bodies.
# In a controller, params is already a HashWithIndifferentAccess:
class OrdersController < ApplicationController
  def create
    # params[:order] is already parsed from the JSON body — no JSON.parse needed
    order_params = params.require(:order).permit(:sku, :quantity, :price)
    @order = Order.create!(order_params)
    render json: @order, status: :created
  end
end

# Parsing an external API response in a service object:
require 'net/http'
require 'json'

class PaymentService
  BASE_URL = "https://api.example.com"

  def charge(amount_cents)
    uri = URI("#{BASE_URL}/charges")
    response = Net::HTTP.post(uri, { amount: amount_cents }.to_json,
                              "Content-Type" => "application/json")
    body = JSON.parse(response.body, symbolize_names: true)

    if response.is_a?(Net::HTTPSuccess)
      body[:charge_id]
    else
      raise "Payment failed: #{body[:error][:message]}"
    end
  rescue JSON::ParserError => e
    raise "Unexpected response format: #{e.message}"
  end
end

# Render a Ruby Hash as a JSON response in Rails:
class ProductsController < ApplicationController
  def show
    product = Product.find(params[:id])
    # render json: automatically calls .to_json and sets Content-Type
    render json: { id: product.id, name: product.name, price: product.price }
  end
end

Rails' params object supports both string and symbol key access viaHashWithIndifferentAccess, so you don't need symbolize_names for controller params. When consuming external JSON APIs inside service objects or background jobs, use symbolize_names: true for cleaner attribute access on the parsed response.

Frequently asked questions

Do I need to install a gem to parse JSON in Ruby?

No. Ruby has included the JSON gem in its standard library since Ruby 1.9.3, released in 2011. Just add require 'json' at the top of your file and JSON.parse is immediately available — no gem install or Gemfile entry needed. In Rails applications, the gem is already loaded by the framework, so you can call JSON.parse without any require statement.

What is the difference between JSON.parse and JSON.load in Ruby?

JSON.parse is the safe, recommended method for parsing untrusted JSON strings. JSON.load is an alias of JSON.parse! — it skips some security checks and can instantiate arbitrary Ruby objects if the JSON contains special class-hint keys, making it dangerous for user-supplied input. Always use JSON.parse unless you fully control and trust the data source.

How do I convert JSON keys to symbols in Ruby?

Pass symbolize_names: true to JSON.parse:

data = JSON.parse(json, symbolize_names: true)
puts data[:name]  # access with symbol key

All string keys in the JSON object — including keys in nested hashes — are recursively converted to Ruby symbols. Symbol lookup is 20–30% faster than string lookup in Ruby's hash implementation, so this is worth enabling for performance-sensitive code paths.

How do I parse a JSON file in Ruby?

Use JSON.load_file('path/to/file.json'), available since Ruby 2.0. It opens the file, reads its contents, and parses the JSON in a single call. For untrusted files, prefer JSON.parse(File.read('path.json')) which goes through the same security checks as a regular JSON.parse call. From Ruby 3.0 onward, JSON.load_file also accepts symbolize_names: true.

How do I handle JSON parse errors in Ruby?

Wrap JSON.parse in a begin/rescue block and catch JSON::ParserError. This exception is raised whenever the input string is not valid JSON — for example, a truncated response, an empty string, or HTML returned instead of JSON. Inspecting e.message gives the exact position and reason for the failure.

begin
  data = JSON.parse(raw_string)
rescue JSON::ParserError => e
  puts "Parse failed: #{e.message}"
end

How do I pretty-print JSON in Ruby?

Call JSON.pretty_generate(obj) instead of JSON.generate(obj) or obj.to_json. pretty_generate adds 2-space indentation and newlines, producing human-readable output. To write it to a file: File.write('out.json', JSON.pretty_generate(obj)). You can also use the pp command in IRB or a Rails console, which calls pretty_generate automatically when printing JSON-like hashes.

Format your JSON

Paste your JSON into Jsonic to validate and format it before parsing in Ruby.

Open JSON Formatter