For reference - taking the unusual step of answering my own question ;-)
- here's one of several ways I could solve this if I just wanted to write lots of Ruby.
def dig?(obj, *args)
arg = args.shift()
return case obj
when Array
if args.empty?
arg >= 0 && arg <= obj.size
else
dig?(obj[arg], *args)
end
when Hash
if args.empty?
obj.key?(arg)
else
dig?(obj[arg], *args)
end
when nil
false
else
raise ArgumentError
end
end
Of course, one could also have opened up classes like Array and Hash and added #dig?
to those, if you prefer core extensions over explicit methods:
class Hash
def dig?(*args)
arg = args.shift()
if args.empty?
self.key?(arg)
else
self[arg]&.dig?(*args) || false
end
end
end
class Array
def dig?(*args)
arg = args.shift()
if args.empty?
arg >= 0 && arg <= self.size
else
self[arg]&.dig?(*args) || false
end
end
end
...which would raise NoMethodError
rather than ArgumentError
if the #dig?
arguments led to a non-Hash/Array node.
Obviously it would be possible to compress those down into more cunning / elegant solutions that use fewer lines, but the above has the benefit of IMHO being pretty easy to read.
In the scope of the original question, though, the hope was to lean more on anything Ruby has out-of-the-box. We've collectively acknowledged early-on that there is no single-method solution, but the answer from @AmazingRein gets close by reusing #dig
to avoid recursion. We might adapt that as follows:
def dig?(obj, *args)
last_arg = args.pop()
obj = obj.dig(*args) unless args.empty?
return case obj
when Array
last_arg >= 0 && last_arg <= obj.size
when Hash
obj.key?(last_arg)
when nil
false
else
raise ArgumentError
end
end
...which isn't too bad, all things considered.
# Example test...
hash = {:one=>1, :two=>[1, 2, 3], :three=>[{:one=>1, :two=>2}, "hello", {:one=>{:two=>{:three=>3}}}]}
puts dig?(hash, :one)
puts dig?(hash, :two, 0)
puts dig?(hash, :none)
puts dig?(hash, :none, 0)
puts dig?(hash, :two, -1)
puts dig?(hash, :two, 10)
puts dig?(hash, :three, 0, :two)
puts dig?(hash, :three, 0, :none)
puts dig?(hash, :three, 2, :one, :two, :three)
puts dig?(hash, :three, 2, :one, :two, :none)