2

I have this "hashed string" that I took from my Rails DB and I have to work with it as a hash. How do I make this

str = "{ H: 50, Q: 25, D: 10, N: 5, P: 1 }"

into this?

str = { H: 50, Q: 25, D: 10, N: 5, P: 1 }

Victor P.
  • 93
  • 1
  • 8
  • Does this answer your question? [How do I convert a String object into a Hash object?](https://stackoverflow.com/questions/1667630/how-do-i-convert-a-string-object-into-a-hash-object) – Ken Y-N Sep 09 '20 at 01:15
  • Not really, Ken. I do not want to use JSON parse and eval in my environment to achieve my desired results. Unfortunately, none of those answers work. I've checked it before. – Victor P. Sep 09 '20 at 01:35
  • @VictorPark Are you stuck with this format, or can you switch to JSON? – Schwern Sep 09 '20 at 02:43

4 Answers4

3

Scanning Strings for Key/Value Pairs

One approach that avoids running eval on potentially-tainted strings is to parse your string for keys and values using String#scan, and then converting the resulting array into a hash using Enumerable#each_slice and Array#to_h. If you like, you can also use Hash#transform_keys to convert the resulting keys from strings to symbols with String#to_sym, and Hash#transform_values to convert each value into an integer with String#to_i.

str = '{ H: 50, Q: 25, D: 10, N: 5, P: 1 }'
str.scan(/\p{Upper}(?=:)|\d+/).each_slice(2).to_h.
  transform_keys(&:to_sym).transform_values &:to_i
#=> {:H=>50, :Q=>25, :D=>10, :N=>5, :P=>1}

The regex used to scan for keys and values is tuned to your sample input, so it might need to be modified for other data. However, it certainly works for your posted example.

Parsing as JSON

In comments, the OP stated that they didn't want to parse the string as JSON. As a general approach, it's conceptually simpler (and shorter) than the solution above, so I'll include it here for other visitors.

The main trick is that the OP's posted input won't parse as JSON unless you quote the keys. We can do that easily with the block form of String#gsub, and then parse the resulting valid JSON into a Ruby hash.

require 'json'

str = '{ H: 50, Q: 25, D: 10, N: 5, P: 1 }'
JSON.parse(str.gsub(/\p{Upper}(?=:)/) { %("#{$&}") }).transform_keys &:to_sym
#=> {:H=>50, :Q=>25, :D=>10, :N=>5, :P=>1}

Both approaches work equally well with the posted example. Parsing the data as JSON seems like it adds useful layers of input validation and programmer convenience, but individual use cases may certainly vary.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
3
str = "{ H: 50, Q: 25, D: 10, N: 5, P: 1 }"
str.gsub(/(\S+): +(\d+)/).with_object({}) { |_,h| h[$1.to_sym] = $2.to_i }
  #=> {:H=>50, :Q=>25, :D=>10, :N=>5, :P=>1}

This employs the form of String#gsub that takes an argument and no block, returning an enumerator that can be chained to Enumerator#with_object. This form of gsub merely generates matches; it makes no substitutions.

One advantage of this construct is that it avoids the creation of temporary arrays.

The regular expression can be written in free-spacing mode to make it self-documenting.

/
(\S+)  # match 1+ characters other than whitespaces, save to capture group 1 
:[ ]+  # match ':' followed by 1+ spaces
(\d+)  # match 1+ digits, save to capture group 2
/x     # free-spacing regex definition mode
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
2

Split the string on non-word characters into array. This splits on 1 or more characters that are not a letter, digit or underscore. Select from the array only words (this removes the first empty string). Slice the array into pairs of keys and values. Convert keys to symbols and values to integers. Convert the result into a hash. The whole sausage looks as follows:

str = '{ H: 50, Q: 25, D: 10, N: 5, P: 1 }'
a = str.split(/\W+/).grep(/\w+/).each_slice(2).map{ |k, v| [k.to_sym, v.to_i] }.to_h
puts a.inspect
# {:H=>50, :Q=>25, :D=>10, :N=>5, :P=>1}
Timur Shtatland
  • 12,024
  • 2
  • 30
  • 47
-1

You can try it eval(str)


> str = "{ H: 50, Q: 25, D: 10, N: 5, P: 1 }"
> eval(str)
> => {:H=>50, :Q=>25, :D=>10, :N=>5, :P=>1}

Minh
  • 49
  • 2
  • 1
    `eval` is almost always the wrong tool since it is incredibly easy for it to lead a remote-code-execution vulnerability where an attacker can make your application execute any attacker-provided code. A restricted parser as proposed in the other answers is usually much safer. – Holger Just Sep 09 '20 at 12:18
  • I agree. Unfortunately eval is not a good tool to do that. – Victor P. Sep 09 '20 at 14:28