[Solved] Regular expression to split ruby strings into multiple hashes


It looks like you tried to create your own JSON serializer, instead of using the one that comes with Ruby. Or, perhaps you didn’t realize that JSON was a thing and wanted to store arrays of hashes and tried to invent your own. Either way, it wasn’t a good path to follow.

This is your definition, which, after being retrieved would remain a String. It appears to be the elements of an Array of Hashes, without the surrounding Array [ and ]:

foo = "{\"monday\"=>\"{:start_time=>9.0, :end_time=>10.5}\"},{\"tuesday\"=>\"{:start_time=>8.5, :end_time=>10.0}\"},{\"wednesday\"=>\"{:start_time=>8.5, :end_time=>10.0}\"}"

and it remains a String:

foo.class # => String

It’d be possible to munge it back into a form that could be evaluated and turned back into an Array of Hashes, but it’s not straightforward, nor is it clean or convenient or elegant.

I suspect your original object looked like this:

foo = {"monday"=>{:start_time=>9.0, :end_time=>10.5}},{"tuesday"=>{:start_time=>8.5, :end_time=>10.0}},{"wednesday"=>{:start_time=>8.5, :end_time=>10.0}}
# => [{"monday"=>{:start_time=>9.0, :end_time=>10.5}},
#     {"tuesday"=>{:start_time=>8.5, :end_time=>10.0}},
#     {"wednesday"=>{:start_time=>8.5, :end_time=>10.0}}]

which is an Array:

foo.class # => Array

It’s an Array now because you have multiple Hash elements separated by commas (,) which Ruby interprets as an Array when assigned to a single variable. This would be a less visually confusing example:

foo = {a: 1}, {a: 2} # => [{:a=>1}, {:a=>2}]
foo.class  # => Array
foo.first.class # => Hash

Back to the Array of Hashes in foo: Starting from that it’s easy to let the JSON class serialize it into a string that can be stored in a database, then retrieved and reparsed back into a Ruby object:

require 'json'
foo.to_json

foo.to_json would result in a serialized string that looks like:

[{"monday":{"start_time":9.0,"end_time":10.5}},{"tuesday":{"start_time":8.5,"end_time":10.0}},{"wednesday":{"start_time":8.5,"end_time":10.0}}]

And, given that string, JSON can rebuild the object:

bar = JSON.parse(foo.to_json)
# => [{"monday"=>{"start_time"=>9.0, "end_time"=>10.5}},
#     {"tuesday"=>{"start_time"=>8.5, "end_time"=>10.0}},
#     {"wednesday"=>{"start_time"=>8.5, "end_time"=>10.0}}]

Note that it’s not necessary to use parse or to_json. The class is smart enough to recognize whether the parameter is a String, or an Array or Hash, and parse or serialize respectively:

JSON[JSON[foo]]
# => [{"monday"=>{"start_time"=>9.0, "end_time"=>10.5}},
#     {"tuesday"=>{"start_time"=>8.5, "end_time"=>10.0}},
#     {"wednesday"=>{"start_time"=>8.5, "end_time"=>10.0}}]

At this point you’re prepared to store the values into a String-type field in your database, then retrieve them later and reuse them.

If you are hoping to search or manipulate those stored JSON strings, I’d recommend re-thinking that. While a modern DBM can search inside JSON and generate it, you’d be better off having a separate table of the keys and values. It’s faster and more flexible.

Finally to your question, how to convert the string so you can get the keys and values:

foo = "{\"monday\"=>\"{:start_time=>9.0, :end_time=>10.5}\"},{\"tuesday\"=>\"{:start_time=>8.5, :end_time=>10.0}\"},{\"wednesday\"=>\"{:start_time=>8.5, :end_time=>10.0}\"}"
bar = eval('[' + foo.gsub(/"([{}])/, '\1') + ']')
bar
# => [{"monday"=>{:start_time=>9.0, :end_time=>10.5}},
#     {"tuesday"=>{:start_time=>8.5, :end_time=>10.0}},
#     {"wednesday"=>{:start_time=>8.5, :end_time=>10.0}}]

bar.map { |h| [h.keys, h.values.first.keys] }
# => [[["monday"], [:start_time, :end_time]],
#     [["tuesday"], [:start_time, :end_time]],
#     [["wednesday"], [:start_time, :end_time]]]

This hints that the Array of Hashes wasn’t the right structure either. A simple hash would have sufficed:

foo = {
  "monday"=>{:start_time=>9.0, :end_time=>10.5},
  "tuesday"=>{:start_time=>8.5, :end_time=>10.0},
  "wednesday"=>{:start_time=>8.5, :end_time=>10.0}
}

require 'json'
bar = JSON[JSON[foo]] # imitate a round-trip to/from the DB
bar.keys # => ["monday", "tuesday", "wednesday"]
bar.values # => [{"start_time"=>9.0, "end_time"=>10.5}, {"start_time"=>8.5, "end_time"=>10.0}, {"start_time"=>8.5, "end_time"=>10.0}]

1

solved Regular expression to split ruby strings into multiple hashes