Hash#dig

Updated April 20, 2026 · Hash Methods
ruby hash nested-data accessor

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

ParameterTypeDescription
keyobjectFirst key to look up in the hash.
*keysobjectAdditional 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