Reputation: 77631
I have a JSON data structure like this...
{
"items": [
{
"person": { // person hash }
},
{
"dog": { // dog hash }
},
{
"fruit": { // fruit hash }
},
{
“person”: { // person hash }
}
]
}
}
Each item in the array contains only one key:value pair. The key is the bot that tells me what type of item the value is.
What I'd like to do is iterate the array and run a different function for each type of item.
So I have something like this...
items = data.dig('items')
items.map do |item|
if person = item.dig('person')
transform_person(person)
elsif dog = item.dig('dog')
transform_dog(dog)
elsif fruit = item.dig('fruit')
transform_fruit(fruit)
end
end
But I feel like there should be a more elegant way to do this?
Apologies. I appear to have left some ambiguity in my question.
The initial array may contain multiple items with the same key. What I am trying to do is map to an array of items that are transformed into what is required by the front end. The input contains a strange structure and info that is not needed by the front end.
So the output array order must match the input array order.
Sorry for the confusion.
Upvotes: 0
Views: 1899
Reputation: 110675
Let f
be a given method that takes as an argument a hash. Without loss of generality, suppose it is as follows. This corresponds to the OP's transform_person
, transform_dog
and transform_fruit
methods combined.
def f(h)
case h.keys.first
when :person then "somebody"
when :dog then "doggie"
when :fruit then "juicy"
end
end
Suppose we are also given (no need for dig
here)
items = data[:items]
#=> [{:person=>{:name=>"Melba"}},
# {:dog=>{:tricks=>[:roll_over, :shake_a_paw]}},
# {:fruit=>{:good=>"raspberries"}}]
and
key_order = [:bird, :marsupial, :dog, :person]
We wish to find the first element k
of key_order
for which items
contains a hash h
for which h.key?(k) #=> true
. If such a hash h
is found we are to then execute f(h)
.
First compute a hash key_map
.
key_map = items.each_with_object({}) { |g,h| h[g.keys.first] = g }
#=> {:person=>{:person=>{:name=>"Melba"}},
# :dog=>{:dog=>{:tricks=>[:roll_over, :shake_a_paw]}},
# :fruit=>{:fruit=>{:good=>"raspberries"}}}
Then we simply execute
k = key_order.find { |k| key_map[k] }
#=> :dog
k ? f(key_map[k]) : nil
#=> "doggie"
Upvotes: 1
Reputation: 211560
First you'll want to define the key preference in a constant:
PECKING_ORDER = %w[ person dog fruit ]
Then you can use that to find it:
def detect(item)
PECKING_ORDER.lazy.map do |key|
[ key, item.dig(key) ]
end.find do |key, v|
v
end
end
Where that can dig up the first item that's found. lazy
is used here so it doesn't dig them all up needlessly, just does them one at a time until there's a hit.
This gives you a key/value pair which you can use with dynamic dispatch:
items.each do |item|
key, value = detect(item)
if (key)
send(:"transform_#{key}", value)
end
end
Upvotes: 2
Reputation: 15957
if you know the mapping, you could make a pseudo factory hash:
methods_mapped = {
"person" => ->(person) { do_something_with_person(person) },
"dog" => ->(dog) { do_something_with_dog(dog) },
"fruit" => ->(fruit) { do_something_with_fruit(fruit) }
}
items.map do |item|
key = item.keys.first # what if keys.size > 1 ?
method = methods_mapped.fetch(key)
method.call(item[key])
end
or you could it from the opposite direction:
methods_mapped.each do |key, method|
method.call(items.dig(key))
end
Upvotes: 1
Reputation: 9693
I would kept it simple:
items.map do |item|
do_something_with_person(item) if item.dig('person')
do_something_with_dog(item) if item.dig('dog')
do_something_with_fruit(item) if item.dig('fruit')
end
or
items.each do |item|
case item
when item.dig('person') then do_something_with_person(item)
when item.dig('dog') then do_something_with_dog(item)
when item.dig('fruit') then do_something_with_fruit(item)
end
end
or
def do_something(item)
case
when item.dig('person') then do_something_with_person(item)
when item.dig('dog') then do_something_with_dog(item)
when item.dig('fruit') then do_something_with_fruit(item)
end
end
items.map { |item| do_something(item) }
Upvotes: 0