CrackedRuby logo

CrackedRuby

JSON Parsing and Generation

Overview

Ruby provides JSON parsing and generation through the JSON module in its standard library. The module converts between Ruby data structures (Hash, Array, String, Numeric, Boolean, nil) and JSON format strings. Ruby's JSON implementation handles the bidirectional transformation while preserving data types where possible.

The primary interface consists of JSON.parse for converting JSON strings to Ruby objects and JSON.generate or to_json for converting Ruby objects to JSON strings. The module also provides streaming parsers for large JSON documents and customizable serialization options.

require 'json'

# Parse JSON string to Ruby hash
json_string = '{"name": "Alice", "age": 30, "active": true}'
data = JSON.parse(json_string)
# => {"name"=>"Alice", "age"=>30, "active"=>true}

# Generate JSON from Ruby hash
hash = { name: "Bob", age: 25, active: false }
JSON.generate(hash)
# => '{"name":"Bob","age":25,"active":false}'

# Alternative generation syntax
hash.to_json
# => '{"name":"Bob","age":25,"active":false}'

The JSON module automatically handles Ruby's core data types. Hash objects become JSON objects, Array objects become JSON arrays, and Ruby's true, false, and nil map directly to their JSON equivalents. Numeric types (Integer, Float) convert to JSON numbers, while String objects become JSON strings with proper escaping.

Ruby stores parsed JSON object keys as strings by default, not symbols. This behavior differs from hash literals but maintains compatibility with JSON's string-based key specification. The module provides options to modify this behavior when parsing.

Basic Usage

The JSON.parse method converts JSON strings into corresponding Ruby data structures. The parser handles nested objects and arrays, maintaining the hierarchical structure of the original JSON.

require 'json'

# Parse simple JSON object
user_json = '{"id": 123, "username": "john_doe", "verified": true}'
user = JSON.parse(user_json)
# => {"id"=>123, "username"=>"john_doe", "verified"=>true}

# Parse JSON array
numbers_json = '[1, 2, 3, 4, 5]'
numbers = JSON.parse(numbers_json)
# => [1, 2, 3, 4, 5]

# Parse nested JSON structure
nested_json = '{
  "users": [
    {"name": "Alice", "roles": ["admin", "editor"]},
    {"name": "Bob", "roles": ["viewer"]}
  ],
  "total": 2
}'
data = JSON.parse(nested_json)
# => {"users"=>[{"name"=>"Alice", "roles"=>["admin", "editor"]}, 
#     {"name"=>"Bob", "roles"=>["viewer"]}], "total"=>2}

The JSON.generate method converts Ruby objects into JSON strings. The method accepts Hash, Array, String, Numeric, Boolean, and nil values, serializing them according to JSON specification.

# Generate JSON from various Ruby objects
person = {
  name: "Sarah",
  age: 28,
  skills: ["Ruby", "Python", "JavaScript"],
  active: true,
  manager: nil
}

json_output = JSON.generate(person)
# => '{"name":"Sarah","age":28,"skills":["Ruby","Python","JavaScript"],"active":true,"manager":null}'

# Generate JSON from array
languages = ["Ruby", "Python", "Go", "Rust"]
JSON.generate(languages)
# => '["Ruby","Python","Go","Rust"]'

# The to_json method provides identical functionality
person.to_json
# => '{"name":"Sarah","age":28,"skills":["Ruby","Python","JavaScript"],"active":true,"manager":null}'

Ruby's JSON module provides options to control parsing and generation behavior. The symbolize_names option converts JSON object keys from strings to symbols during parsing.

json_data = '{"first_name": "John", "last_name": "Smith"}'

# Default parsing (string keys)
parsed_strings = JSON.parse(json_data)
# => {"first_name"=>"John", "last_name"=>"Smith"}

# Parse with symbol keys
parsed_symbols = JSON.parse(json_data, symbolize_names: true)
# => {:first_name=>"John", :last_name=>"Smith"}

The module handles Unicode characters and escape sequences automatically. JSON strings containing escaped characters convert to their unescaped Ruby string equivalents, while Ruby strings with Unicode characters serialize properly to JSON.

# Parse JSON with escaped characters
escaped_json = '{"message": "Hello\\nWorld", "emoji": "\\u2764\\ufe0f"}'
parsed = JSON.parse(escaped_json)
# => {"message"=>"Hello\nWorld", "emoji"=>"❤️"}

# Generate JSON with Unicode
unicode_data = { greeting: "Héllo Wørld! 🌍", count: 42 }
JSON.generate(unicode_data)
# => '{"greeting":"Héllo Wørld! 🌍","count":42}'

Error Handling & Debugging

JSON parsing operations raise JSON::ParserError exceptions when encountering invalid JSON syntax. The error messages indicate the specific parsing failure location and nature.

require 'json'

# Handle malformed JSON
begin
  invalid_json = '{"name": "Alice", "age":}'  # Missing value
  JSON.parse(invalid_json)
rescue JSON::ParserError => e
  puts "JSON parsing failed: #{e.message}"
  # => JSON parsing failed: unexpected token at '}'
end

# Handle unexpected end of input
begin
  incomplete_json = '{"users": [{"name": "Bob"'  # Incomplete structure
  JSON.parse(incomplete_json)
rescue JSON::ParserError => e
  puts "Incomplete JSON: #{e.message}"
  # => Incomplete JSON: unexpected end of JSON input
end

The JSON module provides validation methods to check JSON strings before parsing. The JSON.valid_json? method returns a boolean indicating whether a string contains valid JSON without raising exceptions.

# Validate JSON before parsing
def safe_parse(json_string)
  return nil unless JSON.valid_json?(json_string)
  
  JSON.parse(json_string)
rescue JSON::ParserError
  nil
end

valid_json = '{"status": "success"}'
invalid_json = '{"status": success}'  # Unquoted string value

safe_parse(valid_json)    # => {"status"=>"success"}
safe_parse(invalid_json)  # => nil

Ruby objects that cannot serialize to JSON raise JSON::GeneratorError exceptions during generation. Objects without defined JSON serialization methods cause these errors.

class CustomObject
  def initialize(data)
    @data = data
  end
end

# Attempt to serialize unsupported object
begin
  custom = CustomObject.new("test")
  JSON.generate({object: custom})
rescue JSON::GeneratorError => e
  puts "Cannot serialize: #{e.message}"
end

Complex JSON structures may contain deeply nested objects that exceed parsing limits. The JSON module provides options to control recursion depth and prevent stack overflow errors.

# Handle deeply nested structures
deep_json = '{"level1": {"level2": {"level3": {"level4": "value"}}}}'

begin
  # Parse with depth limit
  result = JSON.parse(deep_json, max_nesting: 3)
rescue JSON::NestingError => e
  puts "Nesting too deep: #{e.message}"
end

Debugging JSON parsing issues often requires examining the exact character position where parsing fails. Ruby's JSON error messages include position information for syntax errors.

# Debug JSON parsing with detailed error information
def debug_json_parse(json_string)
  JSON.parse(json_string)
rescue JSON::ParserError => e
  error_position = e.message.match(/at position (\d+)/)
  if error_position
    position = error_position[1].to_i
    context_start = [0, position - 10].max
    context_end = [json_string.length, position + 10].min
    context = json_string[context_start...context_end]
    
    puts "Parse error near: '#{context}'"
    puts "Error position: #{position}"
  end
  puts "Full error: #{e.message}"
  nil
end

Encoding issues can cause JSON parsing failures when source strings contain invalid UTF-8 sequences. Ruby's string encoding methods help identify and resolve these issues.

# Handle encoding issues
def parse_with_encoding_check(json_string)
  # Ensure valid UTF-8 encoding
  unless json_string.valid_encoding?
    json_string = json_string.encode('UTF-8', 'UTF-8', invalid: :replace)
  end
  
  JSON.parse(json_string)
rescue JSON::ParserError => e
  puts "Encoding or syntax error: #{e.message}"
  nil
end

Performance & Memory

JSON parsing performance depends on document size and structure complexity. Large JSON documents with deep nesting consume more memory and processing time than flat structures.

require 'json'
require 'benchmark'

# Performance comparison between parsing approaches
large_array = (1..10000).map { |i| { id: i, name: "User#{i}", active: i.odd? } }
json_string = JSON.generate(large_array)

Benchmark.bm(15) do |x|
  x.report("Standard parse:") do
    1000.times { JSON.parse(json_string) }
  end
  
  x.report("Symbol keys:") do
    1000.times { JSON.parse(json_string, symbolize_names: true) }
  end
end

Memory usage increases significantly when parsing large JSON documents because Ruby creates all objects in memory simultaneously. For memory-constrained environments, consider streaming approaches or processing JSON in smaller chunks.

# Memory-efficient JSON processing for large datasets
def process_large_json_file(filename)
  File.open(filename, 'r') do |file|
    # Read and process line by line for JSON Lines format
    file.each_line do |line|
      begin
        record = JSON.parse(line.strip)
        yield record if block_given?
      rescue JSON::ParserError
        # Skip invalid lines
        next
      end
    end
  end
end

# Process large dataset without loading everything into memory
process_large_json_file('large_dataset.jsonl') do |record|
  puts record['id'] if record['status'] == 'active'
end

JSON generation performance varies based on object complexity and serialization options. Simple objects with primitive values serialize faster than complex nested structures.

# Compare generation performance for different data structures
simple_data = { id: 1, name: "test", active: true }
complex_data = {
  users: (1..1000).map do |i|
    {
      id: i,
      profile: {
        name: "User #{i}",
        settings: { theme: "dark", notifications: true },
        metadata: { created: Time.now, updated: Time.now }
      }
    }
  end
}

Benchmark.bm(15) do |x|
  x.report("Simple object:") do
    10000.times { JSON.generate(simple_data) }
  end
  
  x.report("Complex object:") do
    100.times { JSON.generate(complex_data) }
  end
end

String allocation becomes a bottleneck when generating large JSON documents. Ruby's JSON module creates intermediate string objects during serialization, which impacts garbage collection.

# Monitor memory usage during JSON operations
def measure_memory_usage(&block)
  GC.start
  memory_before = `ps -o rss= -p #{Process.pid}`.to_i
  
  result = yield
  
  GC.start
  memory_after = `ps -o rss= -p #{Process.pid}`.to_i
  memory_diff = memory_after - memory_before
  
  puts "Memory usage: #{memory_diff} KB"
  result
end

# Test memory usage for JSON generation
large_data = { items: Array.new(50000) { { id: rand(10000), value: rand } } }

measure_memory_usage do
  JSON.generate(large_data)
end

Ruby provides streaming JSON generation through the JSON::Stream::Writer class for applications that need to generate large JSON documents without holding the entire structure in memory.

require 'json/stream'

# Stream large JSON arrays to reduce memory footprint
def generate_large_json_stream(output_file)
  File.open(output_file, 'w') do |file|
    writer = JSON::Stream::Writer.new(file)
    
    writer.start_array
    10000.times do |i|
      writer.value({
        id: i,
        timestamp: Time.now.to_f,
        data: "Record #{i}"
      })
    end
    writer.end_array
  end
end

Production Patterns

Web applications commonly receive JSON data from API requests and database storage. Ruby frameworks provide integration patterns for handling JSON in HTTP request/response cycles.

# Rails controller pattern for JSON APIs
class UsersController < ApplicationController
  def create
    begin
      user_params = JSON.parse(request.body.read)
      user = User.create!(user_params)
      render json: { status: 'success', user: user }, status: 201
    rescue JSON::ParserError
      render json: { error: 'Invalid JSON format' }, status: 400
    rescue ActiveRecord::RecordInvalid => e
      render json: { error: e.message }, status: 422
    end
  end
  
  def index
    users = User.active.limit(100)
    render json: {
      users: users.as_json(only: [:id, :name, :email]),
      total: users.count
    }
  end
end

Configuration management often uses JSON files for storing application settings. Production systems require robust JSON configuration loading with validation and fallback mechanisms.

class ConfigurationManager
  def self.load_config(config_path, environment = 'production')
    config_data = File.read(config_path)
    all_configs = JSON.parse(config_data, symbolize_names: true)
    
    environment_config = all_configs[environment.to_sym]
    raise "Configuration for #{environment} not found" unless environment_config
    
    validate_required_keys(environment_config)
    environment_config
  rescue JSON::ParserError => e
    raise "Invalid JSON in configuration file: #{e.message}"
  rescue Errno::ENOENT
    raise "Configuration file not found: #{config_path}"
  end
  
  private
  
  def self.validate_required_keys(config)
    required_keys = [:database_url, :secret_key, :api_endpoint]
    missing_keys = required_keys - config.keys
    
    unless missing_keys.empty?
      raise "Missing required configuration keys: #{missing_keys.join(', ')}"
    end
  end
end

# Usage in application initialization
begin
  config = ConfigurationManager.load_config('config/app.json', ENV['RAILS_ENV'])
  puts "Loaded configuration for #{ENV['RAILS_ENV']} environment"
rescue => e
  puts "Configuration error: #{e.message}"
  exit 1
end

API clients frequently handle JSON responses from external services. Production applications implement retry logic, timeout handling, and response validation for reliable API integration.

require 'net/http'
require 'uri'
require 'json'

class ApiClient
  def initialize(base_url, timeout: 30)
    @base_url = base_url
    @timeout = timeout
  end
  
  def get_user(user_id)
    uri = URI("#{@base_url}/users/#{user_id}")
    response = make_request(uri)
    
    JSON.parse(response.body, symbolize_names: true)
  rescue JSON::ParserError => e
    raise ApiError, "Invalid JSON response: #{e.message}"
  end
  
  def create_user(user_data)
    uri = URI("#{@base_url}/users")
    
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    http.read_timeout = @timeout
    
    request = Net::HTTP::Post.new(uri)
    request['Content-Type'] = 'application/json'
    request.body = JSON.generate(user_data)
    
    response = http.request(request)
    handle_response(response)
  end
  
  private
  
  def make_request(uri, retries = 3)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    http.read_timeout = @timeout
    
    request = Net::HTTP::Get.new(uri)
    response = http.request(request)
    
    unless response.is_a?(Net::HTTPSuccess)
      raise ApiError, "HTTP #{response.code}: #{response.message}"
    end
    
    response
  rescue Net::TimeoutError => e
    retries -= 1
    retry if retries > 0
    raise ApiError, "Request timeout after #{@timeout} seconds"
  end
  
  def handle_response(response)
    case response
    when Net::HTTPSuccess
      JSON.parse(response.body, symbolize_names: true)
    when Net::HTTPClientError
      error_data = JSON.parse(response.body) rescue { message: response.body }
      raise ApiError, "Client error: #{error_data['message']}"
    when Net::HTTPServerError
      raise ApiError, "Server error: #{response.code} #{response.message}"
    else
      raise ApiError, "Unexpected response: #{response.code} #{response.message}"
    end
  rescue JSON::ParserError
    raise ApiError, "Invalid JSON in response body"
  end
end

class ApiError < StandardError; end

Database interactions often involve storing and retrieving JSON data. Modern databases provide JSON column types that require proper serialization and deserialization handling.

# ActiveRecord model with JSON column handling
class UserPreferences < ActiveRecord::Base
  # Assuming 'preferences' column is JSON type in database
  
  def get_preference(key)
    preferences&.dig(key.to_s)
  end
  
  def set_preference(key, value)
    self.preferences ||= {}
    self.preferences = preferences.merge(key.to_s => value)
  end
  
  def merge_preferences(new_prefs)
    self.preferences = (preferences || {}).merge(new_prefs.stringify_keys)
  end
  
  # Custom serialization for API responses
  def as_json(options = {})
    super(options).tap do |json|
      json['preferences'] = preferences || {}
    end
  end
end

# Usage patterns
user_prefs = UserPreferences.find(user_id)
user_prefs.set_preference(:theme, 'dark')
user_prefs.set_preference(:notifications, { email: true, sms: false })
user_prefs.save!

Common Pitfalls

JSON parsing converts object keys to strings, not symbols, which differs from Ruby hash literal syntax. This behavior causes key access failures when code expects symbol keys.

# Common mistake: assuming symbol keys after parsing
json_data = '{"user_id": 123, "username": "alice"}'
parsed = JSON.parse(json_data)

# This fails because keys are strings
user_id = parsed[:user_id]  # => nil (key doesn't exist)

# Correct approaches
user_id = parsed['user_id']  # => 123
# OR
parsed = JSON.parse(json_data, symbolize_names: true)
user_id = parsed[:user_id]  # => 123

Ruby's nil values serialize to JSON null, but parsing null back returns Ruby nil. However, JSON doesn't distinguish between missing keys and null values, while Ruby hash access does.

# Difference between missing keys and null values
data = { name: "Alice", age: nil }
json_string = JSON.generate(data)  # => '{"name":"Alice","age":null}'

parsed = JSON.parse(json_string)  # => {"name"=>"Alice", "age"=>nil}

# These behave differently
parsed.key?('age')        # => true (key exists with nil value)
parsed.key?('missing')    # => false (key doesn't exist)
parsed['age']             # => nil
parsed['missing']         # => nil (same return value!)

# Better approach for checking existence
def get_value_or_default(hash, key, default = 'N/A')
  if hash.key?(key)
    hash[key] || default  # Handle nil values
  else
    default
  end
end

Floating point precision issues occur when round-trip serializing numeric values through JSON. JSON numbers have different precision characteristics than Ruby's Float class.

# Precision loss with floating point numbers
original_number = 1.7976931348623157e+308
json_string = JSON.generate({ value: original_number })
parsed = JSON.parse(json_string)

parsed['value'] == original_number  # May be false due to precision

# Handle precision-sensitive numbers
require 'bigdecimal'

class PreciseJsonHandler
  def self.generate_with_precision(data)
    # Convert BigDecimal to string for JSON
    processed = convert_decimals_for_json(data)
    JSON.generate(processed)
  end
  
  def self.parse_with_precision(json_string, decimal_keys = [])
    data = JSON.parse(json_string)
    convert_strings_to_decimals(data, decimal_keys)
  end
  
  private
  
  def self.convert_decimals_for_json(obj)
    case obj
    when BigDecimal
      obj.to_s
    when Hash
      obj.transform_values { |v| convert_decimals_for_json(v) }
    when Array
      obj.map { |v| convert_decimals_for_json(v) }
    else
      obj
    end
  end
  
  def self.convert_strings_to_decimals(obj, keys)
    case obj
    when Hash
      obj.transform_values do |v|
        if keys.include?(obj.keys.find { |k| obj[k] == v })
          BigDecimal(v.to_s)
        else
          convert_strings_to_decimals(v, keys)
        end
      end
    when Array
      obj.map { |v| convert_strings_to_decimals(v, keys) }
    else
      obj
    end
  end
end

Time and Date objects require special handling because JSON doesn't define standard datetime formats. Ruby's Time objects serialize to strings but don't automatically parse back to Time objects.

# Time serialization issues
current_time = Time.now
data = { timestamp: current_time, event: "user_login" }

json_string = JSON.generate(data)
# => '{"timestamp":"2024-01-15 10:30:45 -0500","event":"user_login"}'

parsed = JSON.parse(json_string)
parsed['timestamp']  # => "2024-01-15 10:30:45 -0500" (String, not Time)

# Custom Time handling
class TimeAwareJson
  def self.generate(obj)
    processed = convert_times_to_iso8601(obj)
    JSON.generate(processed)
  end
  
  def self.parse(json_string, time_keys = [])
    data = JSON.parse(json_string)
    convert_iso8601_to_times(data, time_keys)
  end
  
  private
  
  def self.convert_times_to_iso8601(obj)
    case obj
    when Time
      obj.iso8601
    when Hash
      obj.transform_values { |v| convert_times_to_iso8601(v) }
    when Array
      obj.map { |v| convert_times_to_iso8601(v) }
    else
      obj
    end
  end
  
  def self.convert_iso8601_to_times(obj, time_keys)
    case obj
    when Hash
      obj.transform_values do |value|
        key = obj.keys.find { |k| obj[k] == value }
        if time_keys.include?(key) && value.is_a?(String)
          Time.parse(value)
        else
          convert_iso8601_to_times(value, time_keys)
        end
      end
    when Array
      obj.map { |v| convert_iso8601_to_times(v, time_keys) }
    else
      obj
    end
  end
end

# Usage
json_string = TimeAwareJson.generate({ created_at: Time.now, name: "test" })
parsed = TimeAwareJson.parse(json_string, ['created_at'])
parsed['created_at'].class  # => Time

Unicode and encoding issues arise when JSON strings contain invalid UTF-8 sequences or when source data has encoding problems. These issues cause parsing failures or data corruption.

# Handle encoding issues gracefully
def safe_json_parse(json_string)
  # Ensure string is properly encoded
  clean_string = json_string.encode('UTF-8', 'UTF-8', 
    invalid: :replace, 
    undef: :replace, 
    replace: '?'
  )
  
  JSON.parse(clean_string)
rescue Encoding::UndefinedConversionError => e
  puts "Encoding conversion failed: #{e.message}"
  nil
rescue JSON::ParserError => e
  puts "JSON parsing failed: #{e.message}"
  nil
end

# Test with problematic encoding
problematic_json = '{"message": "Hello\xC0World"}'.force_encoding('UTF-8')
result = safe_json_parse(problematic_json)

Circular reference errors occur when Ruby objects contain references to themselves or create reference cycles. JSON generation fails when encountering these structures.

# Circular reference handling
class CircularReferenceHandler
  def initialize
    @seen_objects = Set.new
  end
  
  def safe_generate(obj, max_depth = 10, current_depth = 0)
    return '"[Max Depth Reached]"' if current_depth > max_depth
    
    case obj
    when Hash
      return '"[Circular Reference]"' if @seen_objects.include?(obj.object_id)
      @seen_objects.add(obj.object_id)
      
      hash_json = obj.map do |k, v|
        key_json = safe_generate(k, max_depth, current_depth + 1)
        value_json = safe_generate(v, max_depth, current_depth + 1)
        "#{key_json}:#{value_json}"
      end.join(',')
      
      @seen_objects.delete(obj.object_id)
      "{#{hash_json}}"
    when Array
      return '"[Circular Reference]"' if @seen_objects.include?(obj.object_id)
      @seen_objects.add(obj.object_id)
      
      array_json = obj.map do |item|
        safe_generate(item, max_depth, current_depth + 1)
      end.join(',')
      
      @seen_objects.delete(obj.object_id)
      "[#{array_json}]"
    else
      JSON.generate(obj)
    end
  rescue JSON::GeneratorError
    '"[Unserializable Object]"'
  end
end

Reference

Core Methods

Method Parameters Returns Description
JSON.parse(string, **opts) string (String), options (Hash) Object Parse JSON string to Ruby object
JSON.generate(obj, **opts) obj (Object), options (Hash) String Generate JSON string from Ruby object
JSON.valid_json?(string) string (String) Boolean Check if string is valid JSON
#to_json(**opts) options (Hash) String Convert object to JSON string

Parse Options

Option Type Default Description
symbolize_names Boolean false Convert object keys to symbols
max_nesting Integer 100 Maximum nesting depth
allow_nan Boolean false Allow NaN and Infinity values
create_additions Boolean false Create objects from JSON additions

Generate Options

Option Type Default Description
max_nesting Integer 100 Maximum nesting depth
allow_nan Boolean false Allow NaN and Infinity values
indent String nil Indentation string for pretty printing
space String nil Space after colons and commas
space_before String nil Space before colons
object_nl String nil Newline after objects
array_nl String nil Newline after arrays

Exception Classes

Exception Description Common Causes
JSON::ParserError JSON parsing failed Invalid JSON syntax, unexpected tokens
JSON::GeneratorError JSON generation failed Unsupported object types, circular references
JSON::NestingError Exceeded nesting limit Too deeply nested structures

Data Type Mapping

Ruby Type JSON Type Notes
Hash Object Keys converted to strings by default
Array Array Order preserved
String String UTF-8 encoded
Integer/Float Number Precision may be lost with very large numbers
true/false Boolean Direct mapping
nil null Direct mapping
Symbol String Converted to string representation

Common Patterns

# Parse with error handling
begin
  data = JSON.parse(json_string, symbolize_names: true)
rescue JSON::ParserError => e
  # Handle parse error
end

# Generate with pretty printing
JSON.generate(data, indent: '  ', space: ' ', object_nl: "\n", array_nl: "\n")

# Custom serialization
class User
  def as_json(options = {})
    { id: @id, name: @name, email: @email }
  end
  
  def to_json(options = {})
    as_json(options).to_json
  end
end

# Safe parsing with validation
def parse_json_safely(string)
  return nil unless string.is_a?(String) && !string.empty?
  JSON.parse(string)
rescue JSON::ParserError
  nil
end

Performance Considerations

Operation Memory Usage Speed Notes
JSON.parse High for large docs Fast Loads entire structure into memory
JSON.generate Moderate Fast String building overhead
symbolize_names: true Higher Slower Additional symbol creation
Deep nesting High Slower Recursive processing overhead