I have an incoming input which is sometimes an array of hashes and sometimes its just an individual hash.
hsh = {"property"=>[{"name"=>"first_name", "value"=>"Joe"}, {"name"=>"last_name", "value"=>"Doe"}]}
or
hsh = {"property"=>{"name"=>"Foo", "value"=>"Bar"}}
From this input I am trying to generate a hash with name
as key
and value
and as value
, something like this:
hsh['property'].reduce(HashWithIndifferentAccess.new) do |scan, kv_pair|
scan.merge kv_pair['name'] => kv_pair['value']
end
Giving:
{"first_name"=>"Joe", "last_name"=>"Doe"}
This works well when hsh['property']
is an array of hashes but fails with the following error:
TypeError: no implicit conversion of String into Integer
when it's just an hash.
How do I handle the reduce
on hsh['property']
so it handles the case when input to it is hash rather than array of hashes?
It's easy to ensure something being an array. You don't need if-else. Just wrap it in a new array, then flatten that array 1 level down. I have to admit that this is not very performant, but I don't care if what I originally want to do is O(n) or higher (like mapping, reducing, sorting, whatever that requires traversing the array at least once). I won't go this way if what I originally want to do is O(log(n)) or lower (like binary search).
[hsh['property']].flatten(1).reduce(HashWithIndifferentAccess.new) do |scan, kv_pair|
scan.merge kv_pair['name'] => kv_pair['value']
end
The first thing to fix is to get rid of merge
which creates an intermediate Hash each time through the loop. That's a lot of garbage to collect. The second thing is to use the simpler each_with_index
method that doesn't require chaining. This way you can just add data:
hsh['property'].each_with_object(HashWithIndifferentAccess.new) do |kv_pair, scan|
scan[kv_pair['name']] = kv_pair['value']
end
This will work so long as property
has a series of Hash objects in an array. If you have just one you'll need to special case that:
case hsh['property']
when Hash
HashWithIndifferentAccess.new(
hsh['property']['name'] => hsh['property']['value']
)
when Array
hsh['property'].each_with_object(HashWithIndifferentAccess.new) do |kv_pair, scan|
scan[kv_pair['name']] = kv_pair['value']
end
else
# Uh-oh, you've got to handle this case of something random.
end
I'd convert the incoming parameter to an array of a single hash, then proceed:
def foo(aoh)
aoh = [aoh] unless Array === aoh
aoh # return it so we can see it's been changed
# do stuff with the AoH
end
foo({a:1}) # => [{:a=>1}]
foo([{a:1},{b:2}]) # => [{:a=>1}, {:b=>2}]
Once it's an array of a single hash, or multiple hashes, you can iterate over the contents of the array with the same code.
If you are responsible for generating the single hash or an array of hashes, you should always generate the same type of object, an array of hashes, which makes it easier for you to deal with it. If you don't, then convert it to the same type of object and move on.
As you are using rails, there is a dedicated method for ensuring something is an array - Array#wrap
. Also note that your use of #reduce
is considered unindiomatic and you should use #each_with_object
instead.
Here is a possible solution:
Array.wrap(hsh['property']).map { |kv| [kv['name'], kv['value']] }.to_h
If you are using ruby 2.3.0+, you can even take advantage of the cool new Hash#to_proc
:
Array.wrap(hsh['property']).map { |kv| ['name', 'value'].map(&kv) }.to_h
If you insist, you can add the #with_indifferent_access
at the end.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.