Hash#dig
Overview
Hash#dig navigates a chain of keys through nested hashes and arrays, returning nil if any key in the chain is missing or any intermediate value is nil. It eliminates the repetitive h[:key] && h[:key][:nested] && h[:key][:nested][:value] pattern that was the standard approach before dig existed.
dig also works on arrays, so you can mix hash and array access in the same call.
Signature
dig(key, *keys) -> object | nil
Parameters
| Parameter | Type | Description |
|---|---|---|
key | object | First key to look up in the hash. |
*keys | object | Additional keys for nested hashes, or integer indices for arrays. |
Return Value
The value at the end of the chain, or nil if any key or index is not found.
Unlike Hash#fetch, dig never raises an exception for a missing key.
Basic Usage
Deep access in nested hashes
config = {
database: {
primary: {
host: "db1.example.com",
port: 5432
}
}
}
config.dig(:database, :primary, :host) # => "db1.example.com"
config.dig(:database, :primary, :port) # => 5432
config.dig(:database, :replica, :host) # => nil (replica key missing)
Mix with arrays
data = {
users: [
{ name: "Alice", roles: ["admin", "developer"] },
{ name: "Bob", roles: ["developer"] }
]
}
data.dig(:users, 0, :name) # => "Alice"
data.dig(:users, 0, :roles, 0) # => "admin"
data.dig(:users, 1, :roles, 0) # => "developer"
data.dig(:users, 99, :name) # => nil (index 99 does not exist)
Return nil instead of error
response = { user: { address: nil } }
response.dig(:user, :address, :city) # => nil (address is nil, stops there)
response[:user][:address][:city] # => Error: undefined method `[]' for nil:NilClass
Common Use Cases
Parsing API responses
response = JSON.parse(http_body)
user_id = response.dig("data", "user", "id")
created_at = response.dig("data", "user", "metadata", "created_at")
status = response.dig("data", "status") # nil if absent, no error
Safe config access with deep defaults
config = {
logging: {
level: "info",
outputs: [
{ type: "stdout" }
]
}
}
log_level = config.dig(:logging, :level) || "warn"
second_output = config.dig(:logging, :outputs, 1) # nil if no second output
Optional nested attributes
product = {
metadata: {
images: [
{ url: "https://example.com/img1.jpg", alt: "Front view" }
]
}
}
image_url = product.dig(:metadata, :images, 0, :url)
image_alt = product.dig(:metadata, :images, 0, :alt)
missing = product.dig(:metadata, :images, 5, :url) # => nil
Comparison with Alternatives
vs manual chained access
# Without dig — verbose and error-prone
user.dig(:profile, :settings, :theme) rescue nil
# or:
user[:profile] && user[:profile][:settings] && user[:profile][:settings][:theme]
# With dig
user.dig(:profile, :settings, :theme)
vs Hash#fetch
fetch raises KeyError when a key is absent. dig returns nil. When you need to distinguish between a missing key and a nil value, fetch is still the right tool:
h = { key: nil }
h.fetch(:key) # => nil (key exists with nil value)
h.dig(:key) # => nil
h.fetch(:missing) # => KeyError
h.dig(:missing) # => nil
Array access mixed in
# dig can access array indices mid-chain
data = { items: [{ name: "First" }, { name: "Second" }] }
data.dig(:items, 1, :name) # => "Second"
With chained [] access, this works too but is less readable:
data[:items][1][:name] # => "Second"
Gotchas
nil stops the chain. If an intermediate value is nil, dig returns nil rather than continuing:
{ a: nil }.dig(:a, :b, :c) # => nil (stops at :a because it is nil)
This is the intended behavior — it prevents the undefined method '[]' for nil error — but it means you cannot distinguish between “key was nil” and “key was missing” using dig alone.
Integer arguments are array indices. If the value at a key is an array, pass an integer to access by index:
{ list: ["a", "b", "c"] }.dig(:list, 1) # => "b"
{ list: ["a", "b", "c"] }.dig(:list, -1) # => "c" (negative index works too)
dig works on nil. Calling nil.dig(*keys) returns nil without error. This is useful when a variable might be nil:
response = some_api_call() # might return nil
response.dig(:result, :value) # => nil safely
See Also
- /reference/hash-methods/fetch/ — retrieve a value with explicit error handling when key is absent
- /reference/hash-methods/has-key/ — check whether a key exists without retrieving its value
- /reference/hash-methods/keys/ — get all keys from a hash