rubyguides

JSON

The JSON module provides utilities for parsing and generating JSON data. It’s part of Ruby’s standard library, so you don’t need to install anything extra.

Overview

JSON (JavaScript Object Notation) is a lightweight data interchange format. Ruby’s JSON module converts between Ruby objects and JSON strings:

  • Parse: Convert JSON strings into Ruby objects
  • Generate: Convert Ruby objects into JSON strings

Parsing JSON

JSON.parse converts a JSON string into Ruby data structures.

require 'json'

json_string = '{"name": "Alice", "age": 30, "active": true}'
hash = JSON.parse(json_string)

hash["name"]   # => "Alice"
hash["age"]    # => 30
hash["active"] # => true

After parsing, the next step is usually to inspect the resulting Ruby object and decide how much of it the application should trust. That is where JSON becomes useful as an input format instead of just a storage format. The parsed hash can move through validation, mapping, or persistence code with the rest of your data model.

JSON.parse Parameters

ParameterTypeDefaultDescription
stringStringRequiredThe JSON string to parse
optsHash{}Options for parsing

Parse Options

# Parse arrays
JSON.parse('[1, 2, 3]')  # => [1, 2, 3]

# Parse with symbol keys (Ruby 2.5+)
JSON.parse('{"a": 1}', symbolize_names: true)
# => {:a => 1}

# Allow trailing commas
JSON.parse('{"a": 1,}', allow_nan: true)

These options show the two common kinds of control you often need during parsing: shaping the keys and relaxing the parser enough to accept a known source. In practice, it is better to set those behaviors deliberately than to scatter custom parsing rules throughout the app. That keeps the boundary around JSON input easy to spot.

Generating JSON

JSON.generate converts Ruby objects into JSON strings.

require 'json'

hash = { name: "Bob", age: 25, tags: ["ruby", "dev"] }
json_string = JSON.generate(hash)
# => "{\"name\":\"Bob\",\"age\":25,\"tags\":[\"ruby\",\"dev\"]}"

You can also use the alias JSON.dump:

That alias exists because some code reads better when the method name sounds like writing data out instead of building it. The result is the same, so the choice is mostly about tone and consistency. In larger projects, picking one form and using it everywhere keeps the JSON layer easier to scan.

JSON.generate({ key: "value" })
JSON.dump({ key: "value" })  # Same thing

JSON.dump is handy when the surrounding code reads more naturally with the word “dump” than “generate”. Both forms produce the same result, so the choice is mostly about readability. Many codebases pick one style and keep it consistent so developers do not have to pause and wonder which alias to use in a given file.

JSON.generate Parameters

ParameterTypeDefaultDescription
objectObjectRequiredRuby object to convert
optsHash{}Options for generation

Generate Options

# Pretty-printed output
JSON.pretty_generate({ name: "Carol", items: [1, 2, 3] })
# => {
#      "name": "Carol",
#      "items": [
#        1,
#        2,
#        3
#      ]
#    }

# Use symbols as keys
JSON.generate({ a: 1, b: 2 }, symbolize_names: false)
# => "{\"a\":1,\"b\":2}"

The generation options are especially valuable when the output is being read by humans or compared in tests. Pretty printing makes the structure easy to scan, while key handling keeps the data shape predictable for the consumer on the other end. Choosing those options at the call site makes the output contract explicit.

Practical Examples

Working with APIs

require 'json'
require 'net/http'

# Fetch JSON from an API
uri = URI('https://api.example.com/data')
response = Net::HTTP.get(uri)

# Parse the JSON response
data = JSON.parse(response)
puts data["results"]

API responses are a good example of why JSON sits at the edge of many Ruby applications. The incoming data is often trustworthy enough to parse, but still not trustworthy enough to use blindly. A small validation layer after parsing helps turn an external document into application data safely.

Configuration Files

require 'json'

# Read config
config = JSON.parse(File.read('config.json'))

# Write config
File.write('config.json', JSON.pretty_generate(config))

Configuration files usually benefit from the same read-modify-write loop shown here. Parsing gives you a Ruby hash that is easy to inspect and edit, while pretty generation keeps the file pleasant to review in version control. That is a nice pattern for files that humans may update by hand later.

Object Serialization

require 'json'

class User
  attr_accessor :name, :email
  
  def initialize(name, email)
    @name = name
    @email = email
  end
  
  def to_json
    { name: @name, email: @email }.to_json
  end
end

user = User.new("Dan", "dan@example.com")
JSON.generate(user)  # => "{\"name\":\"Dan\",\"email\":\"dan@example.com\"}"

Custom objects often need a small to_json method so they can describe themselves in a stable way. That lets the object control the output format instead of forcing the caller to know its internal structure. It is a clean boundary when an app needs to persist or transmit richer domain objects.

Working Safely With JSON

JSON is a simple interchange format, but parsing external data should still be treated as a separate trust boundary. A good pattern is to parse first, then validate the keys and value shapes you expect. That keeps the code around your API or file input easy to audit.

When you generate JSON, pay attention to values that do not naturally map to JSON types. Symbols, time objects, and custom classes often need a small conversion step so the output stays predictable. A small wrapper method around parsing and generation can keep those rules consistent across the codebase.

See Also