Remove the '-' dividers in JSON keys in Ruby - ruby

I'm trying to read some JSON data from the Tumblr API.
I'm using the Hashie gem to read the values as object properties. This should make reading easier/cleaner.
it turns something like this:
data['post']['title']
into this:
data.post.title
Unfortunately there are some keys showing up with a '-' as divider between like this:
regular-title: Mijn eerste post
format: html
regular-body: <p>post</p>
therefore i cannot use post.regular-title. Is there a way to replace all the minus(-) symbols into underscores(_)?

This will do it:
def convert_object(data)
case data
when Hash
data.inject({}) do |h,(k,v)|
h[(k.respond_to?(:tr) ? k.tr('-', '_') : k)] = convert_object(v)
h
end
when Array
data.map { |i| convert_object(i) }
else
data
end
end
You can use it like this:
convert_object(JSON.parse('{"something-here":"value","otherkey":{"other-key":"value-value"}}'))

Karaszi Istvan helped me a lot with the solution. I added the check for an array in the hash. This way hashes in arrays in the hash will get underscored too.
def convert_hash(hash)
case hash
when Hash
hash.inject({}) do |h,(k,v)|
h[k.tr('-', '_')] = convert_hash(v)
h
end
when Array
array = hash
number = 0
array.each do
array[number] = convert_hash(array[number])
number += 1
end
array
else
hash
end
end
I don't know why i added the 'number' as iterator. Somehow hash.each didn't work.

Related

Best way to iterate a list of hash in ruby

I have data structure like List< HashMap< String, List>>:
records = [{"1"=>[{"account_id"=>"1", "v"=>"1"}, {"account_id"=>"1",
"v"=>"2"}, {"account_id"=>"1", "v"=>"3"}, {"account_id"=>"1",
"v"=>"4"]}, {"2"=>[{"account_id"=>"2", "v"=>"4"}, {"account_id"=>"2",
"v"=>"4"}, {"account_id"=>"2", "v"=>"4"}]}]
I don't care about the keys in hashmap ("1" and "2" in this case), and want to iterate values of map by group:
records.each do |account_map|
account_record = account_map.values[0] # This line
for i in (0 ... account_record.size - 1)
#do something and update account_record[i]
end
end
end
How can I merge account_record = account_map.values[0] into each loop or make it look better. Thanks
Your example is quite confusing, but the regular way to iterate a hash is the following
hash.each do |key, value|
end
So in your example it looks like you should do
records.each do |account_map|
account_map.each do |index, array|
array.each do |hash|
hash['account_id'] # This is how you access your data
hash['v'] # This is how you access your data
end
end
end
Of course you should use better variables names than index, array and hash.

Convert dot notation keys to tree-structured YAML in Ruby

I've sent my I18n files to be translated by a third party. Since my translator is not computer savvy we made a spreadsheet with the keys, they where sent in dot notation and the values translated.
For example:
es.models.parent: "Pariente"
es.models.teacher: "Profesor"
es.models.school: "Colegio"
How can I move that into a YAML file?
UPDATE: Just like #tadman said, this already is YAML. So if you are with the, you are just fine.
So we will focus this question if you would like to have the tree structure for YAML.
The first thing to do is transform this into a Hash.
So the previous info moved into this:
tr = {}
tr["es.models.parent"] = "Pariente"
tr["es.models.teacher"] = "Profesor"
tr["es.models.school"] = "Colegio"
Then we just advanced creating a deeper hash.
result = {} #The resulting hash
tr.each do |k, value|
h = result
keys = k.split(".") # This key is a concatenation of keys
keys.each_with_index do |key, index|
h[key] = {} unless h.has_key? key
if index == keys.length - 1 # If its the last element
h[key] = value # then we only need to set the value
else
h = h[key]
end
end
end;
require 'yaml'
puts result.to_yaml #Here it is for your YAMLing pleasure

In Ruby, group_by where I know there's only 1 element per group

I have a CSV file where one column is a primary key. When I do this:
CSV.read(ARGV[0], headers: true).group_by {|r| r['myKey']}
I get a hash table from key to a list of rows, where the list is always length 1.
Is there a version of group_by which asserts that there's only a single value per key, and creates a hash from key to that single value?
Failing that, is there something like .first which asserts that there's exactly one element in the array/enumerable? I like my scripts to fail when my assumptions are wrong, rather than silently return the wrong thing.
If you use Rails you can use index_by method.
If you know the values r['myKey'] are unique, there's no point in using group_by. As I understand the question, you could do this:
rows = CSV.read(ARGV[0], headers: true)
Hash[rows.map { |r| r['myKey'] }.zip(rows)]
In Ruby 2.0+ the second row could be written:
rows.map { |r| r['myKey'] }.zip(rows).to_h
No. I don't believe there is. But you can solve your problem with each_with_object like so:
CSV.
read(ARGV[0], headers: true).
each_with_object({}) do |r, hash|
key = r['myKey']
value = r
hash[key] = value
end
It's a shame Ruby doesn't have this. Here's what I decided to go on, based on Humza's answer:
module Enumerable
def group_by_uniq
each_with_object({}) do |value, hash|
key = yield value
raise "Multiple values for key \"{key}\"!" unless ! hash.key?(key)
hash[key] = value
end
end
end
If you use your code in you first example you can run this code to check that all hashes are of length 1:
raise 'multiple entries per key!' unless my_hash.values.any?{|val| val.size!=1}
IF you can get the keys into an array you can check that they do not iclude duplicates by:
raise 'multiple entries per key!' unless my_keys.uniq.size == my_keys.size

Pushing keys and values into a hash

I am parsing lines of code that look like Key: Value, and am inserting the Key and Value elements into an array. Then I'm iterating through that array and attempting to store these values into a hash. I want it to be like: "Host => "localhost" and "Content-Length" => "17".
I tried to follow this example:
def parse_headers
#headers = {}
while ! (line = next_line).empty?
header_elements = line.split(': ')
header_elements.each do |key, val|
#headers[key] = val
end
parse_header(line)
end
puts #headers
end
I think my syntax is wrong. When I print the hash, there are only keys, and all values are nil.
If anyone could help, I'd greatly appreciate it.
Did you notice that your resulting hash also contains keys which should be values? That's because header_elements is an array and you're trying to iterate it as a hash (which it isn't). In fact, you don't need to iterate it.
key, val = line.split(': ')
#headers[key] = val
The point is the same as Sergio's answer, but you don't have to use two variables for it. And for safeness, you can add an argument 2.
#headers.store(*line.split(': ', 2))

how to name an object reference (handle) dynamically in ruby

So I have a class like this:
def Word
end
and im looping thru an array like this
array.each do |value|
end
And inside that loop I want to instantiate an object, with a handle of the var
value = Word.new
Im sure there is an easy way to do this - I just dont know what it is!
Thanks!
To assign things to a dynamic variable name, you need to use something like eval:
array.each do |value|
eval "#{value} = Word.new"
end
but check this is what you want - you should avoid using eval to solve things that really require different data structures, since it's hard to debug errors created with eval, and can easily cause undesired behaviour. For example, what you might really want is a hash of words and associated objects, for example
words = {}
array.each do |value|
words[value] = Word.new
end
which won't pollute your namespace with tons of Word objects.
Depending on the data structure you want to work with, you could also do this:
# will give you an array:
words = array.map { |value| Word.new(value) }
# will give you a hash (as in Peter's example)
words = array.inject({}) { |hash, value| hash.merge value => Word.new }
# same as above, but more efficient, using monkey-lib (gem install monkey-lib)
words = array.construct_hash { |value| [value, Word.new ] }

Resources