This commit is contained in:
Daniel Bengl 2025-08-15 11:07:28 +09:00 committed by GitHub
commit 585353a0bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 104 additions and 0 deletions

64
hash.c
View file

@ -4902,6 +4902,69 @@ rb_hash_dig(int argc, VALUE *argv, VALUE self)
return rb_obj_dig(argc, argv, self, Qnil);
}
/*
* call-seq:
* h.safe_dig(key, ...) -> object or nil
*
* Traverses the given keys and returns the value found or +nil+ if any step
* is not a Hash or Array, or the key/index is missing.
*
* Accepts both String and Symbol keys for Hash lookup.
*
* { foo: { bar: 1 } }.safe_dig(:foo, :bar) #=> 1
* { foo: "none" }.safe_dig(:foo, :bar) #=> nil
* { items: [{id: 1}, {id: 2}] }.safe_dig(:items, 1, :id) #=> 2
*
*/
static VALUE
rb_safe_hash_dig(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
VALUE curr = self;
for (; argc > 0; argv++, argc--)
{
VALUE key = *argv, val = Qundef;
if (RB_TYPE_P(curr, T_HASH))
{
val = rb_hash_lookup2(curr, key, Qundef);
if (val == Qundef)
{
if (RB_TYPE_P(key, T_SYMBOL))
{
val = rb_hash_lookup2(curr, rb_sym2str(key), Qundef);
}
else if (RB_TYPE_P(key, T_STRING))
{
val = rb_hash_lookup2(curr, ID2SYM(rb_intern_str(key)), Qundef);
}
}
}
else if (RB_TYPE_P(curr, T_ARRAY))
{
if (RB_INTEGER_TYPE_P(key))
{
val = rb_ary_entry(curr, NUM2LONG(key));
}
else
{
return Qnil;
}
}
else
{
return Qnil;
}
if (val == Qundef) return Qnil;
if (argc == 1) return val;
if (NIL_P(val)) return Qnil;
curr = val;
}
return curr;
}
static int
hash_le_i(VALUE key, VALUE value, VALUE arg)
{
@ -7468,6 +7531,7 @@ Init_Hash(void)
rb_define_method(rb_cHash, "any?", rb_hash_any_p, -1);
rb_define_method(rb_cHash, "dig", rb_hash_dig, -1);
rb_define_method(rb_cHash, "safe_dig", rb_safe_hash_dig, -1);
rb_define_method(rb_cHash, "<=", rb_hash_le, 1);
rb_define_method(rb_cHash, "<", rb_hash_lt, 1);

View file

@ -0,0 +1,40 @@
require_relative '../../spec_helper'
describe "Hash#safe_dig" do
it "returns the value for a valid path" do
h = { foo: { bar: "baz" } }
h.safe_dig(:foo, :bar).should == "baz"
end
it "returns nil when an intermediate value is not a hash or array" do
h = { foo: "value" }
h.safe_dig(:foo, :bar).should == nil
end
it "returns nil when the index is out of bounds" do
h = { items: [1, 2, 3] }
h.safe_dig(:items, 5).should == nil
end
it "works with arrays in the path" do
h = { items: [{ id: 1 }, { id: 2 }] }
h.safe_dig(:items, 1, :id).should == 2
end
it "accepts string and symbol keys" do
h = { "user" => { "name" => "Dani" } }
h.safe_dig(:user, :name).should == "Dani"
end
it "returns nil when receiver key is nil" do
h = { user: nil }
h.safe_dig(:user, :name).should == nil
end
it "does not modify the receiver" do
h = { foo: { bar: "baz" } }
original = h.dup
h.safe_dig(:foo, :bar)
h.should == original
end
end