Working with JSON in Ruby
Ruby has built-in support for JSON through the json library.
This guide covers parsing, generation, validation, and best practices for handling JSON in Ruby.
Parsing JSON
Use JSON.parse() to convert JSON strings to Ruby objects:
require 'json'
# Parse JSON string to Hash
json_string = '{"name": "John", "age": 30, "city": "New York"}'
data = JSON.parse(json_string)
puts data["name"] # John
puts data["age"] # 30
puts data.class # Hash
# Parse JSON array
json_array = '[1, 2, 3, "four", true, null]'
items = JSON.parse(json_array)
puts items.inspect # [1, 2, 3, "four", true, nil]
# Parse with symbol keys
data = JSON.parse(json_string, symbolize_names: true)
puts data[:name] # John
puts data[:age] # 30 Generating JSON
Use JSON.generate() or .to_json to convert Ruby objects to JSON:
require 'json'
user = {
name: "Jane",
age: 25,
is_active: true,
hobbies: ["reading", "coding"],
address: nil
}
# Basic generation
json = JSON.generate(user)
puts json
# {"name":"Jane","age":25,"is_active":true,"hobbies":["reading","coding"],"address":null}
# Using to_json method
json = user.to_json
puts json
# Pretty print
pretty_json = JSON.pretty_generate(user)
puts pretty_json
# {
# "name": "Jane",
# "age": 25,
# "is_active": true,
# "hobbies": [
# "reading",
# "coding"
# ],
# "address": null
# }
# Custom indentation
json = JSON.pretty_generate(user, indent: ' ', space: ' ')
puts json Working with Files
require 'json'
# Read JSON file
json_content = File.read('data.json')
data = JSON.parse(json_content)
# Or in one line
data = JSON.parse(File.read('data.json'))
# Write JSON file
user = { name: "John", age: 30 }
File.write('output.json', JSON.pretty_generate(user))
# Read and parse with error handling
def read_json_file(path)
JSON.parse(File.read(path), symbolize_names: true)
rescue Errno::ENOENT
puts "File not found: #{path}"
nil
rescue JSON::ParserError => e
puts "Invalid JSON: #{e.message}"
nil
end
# Append to existing JSON file
def append_to_json_array(path, new_item)
data = JSON.parse(File.read(path)) rescue []
data << new_item
File.write(path, JSON.pretty_generate(data))
end JSON Validation
require 'json'
def valid_json?(string)
JSON.parse(string)
true
rescue JSON::ParserError
false
end
puts valid_json?('{"name": "John"}') # true
puts valid_json?('{invalid}') # false
# Detailed validation
def validate_json(string)
data = JSON.parse(string)
{
valid: true,
data: data,
type: data.class.name
}
rescue JSON::ParserError => e
{
valid: false,
error: e.message
}
end
result = validate_json('{"name": "John"}')
puts result
# {:valid=>true, :data=>{"name"=>"John"}, :type=>"Hash"}
result = validate_json('{invalid}')
puts result
# {:valid=>false, :error=>"unexpected token at '{invalid}'"} Custom JSON Serialization
require 'json'
require 'date'
class User
attr_accessor :name, :age, :email, :created_at
def initialize(name, age, email)
@name = name
@age = age
@email = email
@created_at = DateTime.now
end
# Define how to convert to JSON
def to_json(*args)
{
name: @name,
age: @age,
email: @email,
created_at: @created_at.iso8601
}.to_json(*args)
end
# Class method to create from JSON
def self.from_json(json_string)
data = JSON.parse(json_string, symbolize_names: true)
user = new(data[:name], data[:age], data[:email])
user.created_at = DateTime.parse(data[:created_at]) if data[:created_at]
user
end
end
# Usage
user = User.new("John", 30, "john@example.com")
json = user.to_json
puts json
# {"name":"John","age":30,"email":"john@example.com","created_at":"2024-01-15T10:30:00+00:00"}
# Recreate from JSON
user2 = User.from_json(json)
puts user2.name # John Working with APIs
require 'json'
require 'net/http'
require 'uri'
# GET request
def fetch_json(url)
uri = URI.parse(url)
response = Net::HTTP.get_response(uri)
if response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
else
raise "HTTP Error: #{response.code}"
end
end
data = fetch_json('https://api.example.com/users')
puts data
# POST request with JSON body
def post_json(url, data)
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
request = Net::HTTP::Post.new(uri.path)
request['Content-Type'] = 'application/json'
request.body = data.to_json
response = http.request(request)
JSON.parse(response.body)
end
result = post_json('https://api.example.com/users', {
name: 'John',
email: 'john@example.com'
})
# Using Faraday gem (recommended for production)
# gem install faraday
require 'faraday'
conn = Faraday.new(url: 'https://api.example.com') do |f|
f.request :json
f.response :json
end
response = conn.get('/users')
puts response.body # Already parsed as Ruby Hash
response = conn.post('/users', { name: 'John' })
puts response.body JSON with ActiveRecord (Rails)
# In Rails, ActiveRecord models automatically support JSON serialization
class User < ApplicationRecord
# Serialize a column as JSON
serialize :preferences, JSON
# Or in Rails 5+
serialize :preferences, coder: JSON
# Store accessor for JSON column (PostgreSQL/MySQL JSON columns)
store :settings, accessors: [:theme, :notifications], coder: JSON
end
# Usage
user = User.new
user.preferences = { theme: 'dark', language: 'en' }
user.save
# Access nested JSON
user.preferences['theme'] # 'dark'
# Store accessors
user.theme = 'light'
user.notifications = true
# Render as JSON in controller
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
render json: @user
end
def index
@users = User.all
render json: @users, only: [:id, :name, :email]
end
end JSON Schema Validation
# gem install json-schema
require 'json-schema'
schema = {
"type" => "object",
"required" => ["name", "email"],
"properties" => {
"name" => { "type" => "string", "minLength" => 1 },
"age" => { "type" => "integer", "minimum" => 0 },
"email" => { "type" => "string", "format" => "email" }
}
}
# Validate data
data = { "name" => "John", "email" => "john@example.com", "age" => 30 }
if JSON::Validator.validate(schema, data)
puts "Valid!"
else
puts "Invalid!"
end
# Get validation errors
errors = JSON::Validator.fully_validate(schema, data)
puts errors.inspect
# Validate with exception
begin
JSON::Validator.validate!(schema, data)
rescue JSON::Schema::ValidationError => e
puts "Validation error: #{e.message}"
end Performance with Oj
# gem install oj
require 'oj'
# Oj is 2-3x faster than the standard JSON library
# Parse JSON
data = Oj.load('{"name": "John", "age": 30}')
# Generate JSON
json = Oj.dump({ name: "Jane", age: 25 })
# Pretty print
json = Oj.dump({ name: "Jane" }, indent: 2)
# Different modes
Oj.default_options = { mode: :compat } # Compatible with JSON gem
Oj.default_options = { mode: :object } # Preserve Ruby object types
# Use as drop-in replacement
require 'oj'
Oj.mimic_JSON()
# Now JSON.parse and JSON.generate use Oj
data = JSON.parse('{"name": "John"}')
json = JSON.generate({ name: "Jane" }) Common Patterns
require 'json'
# Deep symbolize keys
def deep_symbolize_keys(hash)
hash.transform_keys(&:to_sym).transform_values do |value|
case value
when Hash then deep_symbolize_keys(value)
when Array then value.map { |v| v.is_a?(Hash) ? deep_symbolize_keys(v) : v }
else value
end
end
end
# Merge JSON objects
def merge_json(json1, json2)
data1 = JSON.parse(json1)
data2 = JSON.parse(json2)
data1.merge(data2).to_json
end
# Extract nested value safely
def dig_json(json_string, *keys)
JSON.parse(json_string).dig(*keys)
rescue JSON::ParserError
nil
end
# Usage
json = '{"user": {"address": {"city": "NYC"}}}'
city = dig_json(json, "user", "address", "city") # "NYC" Best Practices
- Use
symbolize_names: truefor cleaner Ruby code - Always handle
JSON::ParserErrorwhen parsing untrusted input - Use
JSON.pretty_generatefor human-readable output - Consider using
Ojgem for better performance - Implement
to_jsonand class methods for custom serialization - Use JSON schema validation for complex data structures
- In Rails, use
render json:for API responses