0

I have an hash like below:

hash = {"a": [{"c": "d", "e": "f"}] }

Normally we can access it like hash["a"][0]["c"].

But, I have a string like:

string = "a[0]['c']"

(This can change depending upon user input)

Is there any easy way to access hash using above string values?

7
  • Can the hash keys be anything, or is there some restriction e.g. a-zA-Z0-9_? Commented Dec 6, 2016 at 14:40
  • 1
    For that matter, are the hash keys always symbols? Commented Dec 6, 2016 at 14:49
  • It can be anything Commented Dec 6, 2016 at 14:58
  • In that case, none of the answers below will work (although they're excellent for limited input) and you're going to have a difficult time coming up with one that works safely for "anything." Commented Dec 6, 2016 at 15:02
  • @Jordan Yes. To begin with I can restrict hash to symbol or index but later on I will need to find a solution for it. Commented Dec 6, 2016 at 15:09

3 Answers 3

2

Assuming that the user inputs numbers for array indices and words for hash keys:

keys = string.scan(/(\d+)|(\w+)/).map do |number, string|
  number&.to_i || string.to_sym
end

hash.dig(*keys) # => "d"
Sign up to request clarification or add additional context in comments.

14 Comments

Interesting example. I didn't know dig could iterate through a mix of Arrays and Hashes.
@EricDuminil, yea, it's awesome like that. It also works for structs. I really hope they add an Indexible module, where you have to implement #[] and it gives you other goodies like #dig.
@ndn You got me to wondering if dig will "dig" into any object that responds to dig—indeed it will! eval.in/691998 That's handy.
@Jordan, this is pretty much how it's implemented in the existing classes too. That is why I was hoping for an Indexible module in the future. (:
|
1

You could do this :

hash = { 'a' => [{ 'c' => 'd', 'e' => 'f' }] }

string = "a[0]['c']"

def nested_access(object, string)
  string.scan(/\w+/).inject(object) do |hash_or_array, i|
    case hash_or_array
    when Array then hash_or_array[i.to_i]
    when Hash then hash_or_array[i] || hash_or_array[i.to_sym]
    end
  end
end

puts nested_access(hash, string) # => "d"

The input string is scanned for letters, underscores and digits. Everything else is ignored :

puts nested_access(hash, "a/0/c") #=> "d"
puts nested_access(hash, "a 0 c") #=> "d"
puts nested_access(hash, "a;0;c") #=> "d"

An incorrect access value will return nil.

It also works with symbol as keys :

hash = {a: [{c: "d", e: "f"}]}
puts nested_access(hash, "['a'][0]['c']")

It brings the advantage of being not too strict about user input, but it does have the drawback of not recognizing keys with spaces.

6 Comments

{ "Hash keys aren't so \"predictable\"" => 1 }
You're totally right, it won't work in this case. I fear that with a more restricted syntax, not many users, if any, would be able to use this key. Should the code be always correct but hard to use, or often correct and more forgiving of user input? That's for the OP to decide.
@EricDuminil The thing is user will input in a same way. Like user will be given a hash and he has to input the path to reach a particular value.
@EricDuminil You're right, although the hash syntax is fine in Ruby 2.2+—{"a": 1} == {a: 1} == {:a => 1}. There's not really a good solution here if arbitrary hash keys are allowed. P.S. \w includes _, so [\w_] is redundant.
@Jordan : Thanks a lot. You're right, the hash syntax is correct for Ruby 2.3.1, but not Ruby 2.1.5.
|
0

you can use gsub to clean other characters into an array and use that array to access your hash

hash = {"a": [{"c": "d", "e": "f"}] }

string = "a[0]['c']"

tmp = string.gsub(/[\[\]\']/, '').split('')
#=> ["a", "0", "c"]

hash[tmp[0].to_sym][tmp[1].to_i][tmp[2].to_sym]
#=> "d"

1 Comment

Thanks for the reply. But, it is not necessary that the second argument is an integer. Above one is just an example and hash and input string can be in any form.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.