Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
72: JSON Module In Depth
I've spent a lot of time reviewing PRs where the developer just called JSON.parse(response_body) and then spent the next three hours wondering why their hash lookups were returning nil. It's a rite of passage in Ruby. You expect your keys to be symbols because that's how we write Ruby hashes, but JSON only knows strings. If you aren't careful, you end up with a codebase littered with hash["user_id"] in some places and hash[:user_id] in others, which is a recipe for a midnight production bug.
The friction of string keys
The naive way to handle a JSON response is to just parse it and hope for the best. Let's say we're pulling a user profile from an API. If you do this:
require 'json'
json_data = '{"id": 101, "username": "jdoe", "email": "jane@example.com"}'
user = JSON.parse(json_data)
puts user[:username] # => nil
You'll get nil. Why? Because JSON.parse returns a hash with string keys by default. You could fix this by changing your access to user["username"], but that feels clunky in a Ruby project. The better way is to tell the parser to symbolize the names right at the gate.
user = JSON.parse(json_data, symbolize_names: true)
puts user[:username] # => "jdoe"
I always recommend symbolize_names: true for internal API consumption. It keeps your data structures consistent with the rest of your Ruby logic. The only trade-off is memory; in very old versions of Ruby, symbols weren't garbage collected, so symbolizing keys from a massive, untrusted JSON file could lead to a memory leak. In modern Ruby, that's mostly a non-issue, but it's still a good reason to be mindful of the size of the payload you're symbolizing.
The security trap of .load
You'll see JSON.load in some older tutorials, and it looks like a drop-in replacement for JSON.parse. It isn't. I cannot stress this enough: never use JSON.load on data coming from an external source. Here is the reason why.
JSON.parse is designed to take a string and turn it into basic Ruby types: arrays, hashes, strings, and numbers. JSON.load, however, is more powerful—and dangerous. It can instantiate arbitrary Ruby objects if the JSON is formatted specifically to do so. If an attacker sends you a specially crafted JSON string, JSON.load could potentially trigger the execution of code or create objects that crash your system. Stick to JSON.parse for anything that touches the network.
Serialization and the custom object problem
When it's time to send data back out, the naive approach is to just throw your objects into a hash and call JSON.generate. But Ruby objects don't magically know how to represent themselves as JSON.
class User
attr_accessor :name, :email
def initialize(name, email)
@name, @email = name, email
end
end
me = User.new("Alice", "alice@ruby.org")
puts JSON.generate({ user: me }) # => {"user":"#" }
That's useless. You've just serialized the object's memory address. To do this properly, you should implement a to_json method or, more commonly, a method that returns a hash of the data you actually want to expose. The JSON module looks for a to_json method on objects. If you define it, you control exactly what the outside world sees.
class User
def as_json(options = {})
{
name: @name,
email: @email
}
end
def to_json(*options)
as_json(*options).to_json
end
end
puts JSON.generate({ user: me }) # => {"user":{"name":"Alice","email":"alice@ruby.org"}}
I prefer separating as_json from to_json. as_json creates the "blueprint" (the hash), and to_json handles the actual string conversion. This makes it much easier to nest your objects inside larger structures without the serialization breaking.
📋 Practical Task
Build a Secure User-Settings Synchronizer
Create a Ruby script that simulates synchronizing user settings between a local object and a JSON string received from a remote server. Your task is to implement the following:
- Create a
UserSettingsclass that holdstheme,notifications_enabled, andfont_size. - Implement a
to_jsonmethod in theUserSettingsclass so it can be serialized correctly. - Write a
sync_from_json(json_string)method that takes a JSON string and returns a newUserSettingsobject. Requirement: You must useJSON.parsewithsymbolize_names: trueto ensure the keys are symbols. - Demonstrate a "failed" sync by attempting to pass a malformed JSON string to your method and rescuing the
JSON::ParserErrorto print a friendly warning message instead of crashing.
There are no comments for now.