Skip to content

[WIP] Add safe dig #14203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

CuddlyBunion341
Copy link

@CuddlyBunion341 CuddlyBunion341 commented Aug 13, 2025

Navigating deeply nested hashes in Ruby can be cumbersome, especially when working with unpredictable API responses or other external data sources.

When accessing data, you may only want the result if you can traverse the entire path without encountering intermediate errors. Many APIs return inconsistent structures, sometimes nested hashes and sometimes plain strings or other types, which makes this process error prone.

The built in dig method does not handle mixed types gracefully:

my_api_response = { status: "none" }
my_api_response.dig(:status, :code)
# => TypeError: String does not have #dig method (TypeError)

A safer approach would be a method that performs these checks internally and stops when it cannot dig further, returning nil instead of raising an error.

safe_dig overview

safe_dig traverses a path through nested structures and returns nil as soon as it cannot continue. It never raises an error when an intermediate value is not a hash or an array, and it does not require the receiver to implement dig.

Examples

Proper structure

my_api_response = { status: { code: 200, name: "ok" } }
my_api_response.safe_dig(:status, :code)
# => 200

Improper structure with mixed types

my_api_response = { status: "none" }
my_api_response.safe_dig(:status, :code)
# => nil

Arrays in the path

payload = { items: [{ id: 1 }, { id: 2 }] }
payload.safe_dig(:items, 1, :id)
# => 2
payload.safe_dig(:items, 5, :id)
# => nil

String and symbol keys

h = { "user" => { "name" => "Dani" } }
h.safe_dig(:user, :name)
# => "Dani"

Non container mid path

h = { user: nil }
h.safe_dig(:user, :name)
# => nil

View specs for more details

This comment has been minimized.

@nobu
Copy link
Member

nobu commented Aug 13, 2025

  1. New features need discussions at the ITS.
  2. The word “safe” feels improper as the behavior not to raise an exception.
  3. Other than Hash and Array are not supported.
  4. Mixing Symbol and String as Hash keys is bad.

Many APIs return inconsistent structures, sometimes nested hashes and sometimes plain strings or other types, which makes this process error prone.

Is it so common?

@CuddlyBunion341
Copy link
Author

  1. Thanks for the notice @nobu.
    I'll start a conversation on the official issue tracker.

  2. The naming was inspired by safe_navigate. Which does not raise either.

  3. Regarding the other types:
    The idea would be to dig only on such objects?
    Why would I want to safe_dig on a string or an integer?

Maybe it makes sense to implement dig on the object level..

  1. Also thanks for the remark regarding the mixing of key types.
    What would be your proposed solution to retrieving hash data?

@rnestler
Copy link

rnestler commented Aug 15, 2025

A better name could be try_dig? Similar to the rails helper try compared to the safe navigator:

[1] pry(main)> "foo".try(:length)
=> 3
[2] pry(main)> "foo".try(:bar)
=> nil
[3] pry(main)> nil.try(:length)
=> nil
[1] pry(main)> "foo"&.length
=> 3
[2] pry(main)> "foo"&.bar
NoMethodError: undefined method `bar' for an instance of String (NoMethodError)

"foo"&.bar
     ^^^^^
from (pry):2:in `__pry__'
[3] pry(main)> nil&.length
=> nil

@CuddlyBunion341
Copy link
Author

Thanks for the suggestion @rnestler!
try_dig does make more sense than safe_dig regarding the error handling behavior.

I have opened a feature request regarding this functionality:
https://bugs.ruby-lang.org/issues/21545

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants