1

I have a string like this "{ssl:true,sslAllowInvalidCertificates:true}"

(please note that the string can contain any no. of key/value pairs)

I want to convert this into hash, in Ruby, like this:

{ssl:true,sslAllowInvalidCertificates:true}

(Please note that the output is to be exactly similar to the above. It should not be in 'generic' hash notation like

{"ssl" => "true","sslAllowInvalidCertificates" => "true"}

The MongoDB client library can recognize the option only if it is exactly same as per requirement, else throws error.

How to do this in ruby?

TIA!

djromero
  • 19,551
  • 4
  • 71
  • 68
sanar
  • 437
  • 9
  • 22
  • Where is the string coming from? Do you trust the source of the string to only provide a hash in a valid format? Or is there be the risk that the string might contain a dangerous Ruby command? – spickermann Oct 17 '20 at 06:31
  • 2
    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) – dcangulo Oct 17 '20 at 06:33
  • Hi @spickermann yes the source is valid.. calling from a local code – sanar Oct 17 '20 at 06:35
  • Hi @dcangulo sorry, my requirement is different.. the inputs format is exactly similar to one I have put is my question and so is output.. Also, I cannot use any external libraries and JSON. – sanar Oct 17 '20 at 06:36
  • For the example, both values in the resulting hash are `true`. What other values could they be? Could they be `false`, `nil`, strings, symbols, integers, arrays, hashes, ranges and so on? – Cary Swoveland Oct 17 '20 at 06:47
  • @NetTechie Can you explain what is a non-generic hash in ruby? A snippet of ruby that produces one will be great to really understand what you want. – djromero Oct 17 '20 at 07:03
  • @NetTechie You are saying that `eval("{ssl:true,sslAllowInvalidCertificates:true}")` isn't working for you because it returns a "generic" hash? What exactly is not working with that hash? What is the error message? – spickermann Oct 17 '20 at 07:17

2 Answers2

2

TL;DR

To convert your String into a Hash, you either have to parse it yourself, or call Kernel#eval on it. Either way, your real issue seems to be round-tripping this back to a string in your expected format. One way to do that is to re-open the Hash class and add a custom output method, rather than relying on the Hash#to_s method aliased to Hash#inspect.

String to Hash

If you trust the data source and have some mechanism to sanitize or check the String for potential arbitrary code execution, #eval is certainly the easiest thing you can do. I'd personally add a tiny bit of safety by making sure the String isn't tainted first, though. For example:

str = "{ssl:true,sslAllowInvalidCertificates:true}"

raise "string tainted: #{str}" if str.tainted?
hsh = eval str
#=> {:ssl=>true, :sslAllowInvalidCertificates=>true}

However, if you don't trust the source or structure of your String, you can parse and validate the information yourself using some variant of the following as a starting point:

hsh = Hash[str.scan(/\w+/).each_slice(2).to_a]
#=> {:ssl=>true, :sslAllowInvalidCertificates=>true}

Hash to Custom String

If you then want to dump it back out to your custom format as a String, you can monkeypatch the Hash class or add a singleton method to a given Hash instance to provide a #to_mongo method. For example:

class Hash
  def to_mongo
    str = self.map { |k, v| '%s:%s' % [k, v] }.join ?,
    '{%s}' % str
  end
end

Calling this method on your Hash instance will yield the results you seem to want:

hsh.to_mongo
#=> "{ssl:true,sslAllowInvalidCertificates:true}"
Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • Thanks for all your suggestions..a few points for clarifications: – sanar Oct 18 '20 at 03:44
  • Thanks for all your suggestions..a few points for clarifications: 1) I am absolutely new to Ruby.. I had to do this because I need to connect to Mongo server from logstash conf.. The DB (or server?) can, as of now, be connected with only invoking a new URI (connection string) with option {ssl:true,sslAllowInvalidCertificates:true} – sanar Oct 18 '20 at 03:51
  • I have tried all other format of options: {"ssl":"true","sslAllowInvalidCertificates":"true"} , {:ssl=>true, :sslAllowInvalidCertificates=>true}, etc. The Mongo client simply rejects all such options and expects only { ssl:true, sslAllowInvalidCertificates:true}. As suggested by some of you, eval() produces {:ssl=>true, :sslAllowInvalidCertificates=>true} which throws error. – sanar Oct 18 '20 at 03:51
  • I think the confusion was because I mentioned "generic", apologies, I should have used "usual", as almost all articles / tutorials I read about hash used only the 'fat-arrow' notation as pointed out by @Joshua Pereira – sanar Oct 18 '20 at 03:58
1

It seems there is some confusion surrounding the fat arrow syntax for hashes in ruby. You should be able to run eval on the string to generate the following hash:

{:ssl=>true, :sslAllowInvalidCertificates=>true}

You mention that the output cannot be in "generic" hash notation, which I assume is referring to the fat arrow notation used in your example.

Since Ruby 1.9, a new syntax can be used to create a hash

{foo: "bar"}

rather than the previous

{:foo => "bar"}

Interactive ruby consoles, such as irb and pry, try to print human friendly strings for the hash. Creating a hash with either of the two previous syntaxes will produce the same result in the console:

{:foo=>"bar"}

However, in memory, both of the objects are equivalent.

(There is the caveat that your "generic" hash example uses strings as keys. If that's what you're referring to, you can call #symbolize_keys on the hash)

Joshua Pereira
  • 191
  • 1
  • 7