How do I iterate over this hash selectively? - ruby

{
"menu": {
"header": "menu",
"items": [
{"id": 27},
{"id": 0, "label": "Label 0"},
null,
{"id": 93},
{"id": 85},
{"id": 54},
null,
{"id": 46, "label": "Label 46"}
]
}
}
Above is the JSON that I am trying to iterate through. Essentially, I would like to identify the value of key "id" if that hash also has a "label" key.
So the above would return 0 and 46 as well.
I am stuck here:
require 'json'
line = '{"menu": {"header": "menu", "items": [{"id": 27}, {"id": 0, "label": "Label 0"}, null, {"id": 93}, {"id": 85}, {"id": 54}, null, {"id": 46, "label": "Label 46"}]}}'
my_parse = JSON.parse(line)
items = my_parse['menu']['items'].compact.select { |item| item['label'] }
puts items.inject

Use Array#select to identify the elements that have both "id" and "label" then Array#map to pluck only the "ids".
hash = JSON.parse(your_json_string)
hash['menu']['items'].select { |h| h && h['id'] && h['label'] }.map {|h| h['id']}
# => [0, 46]
A more cleaned up version could look like this
def ids_with_label(json_str)
hash = JSON.parse(json_str)
items = hash['menu']['items']
items_with_label = items.select { |h| h && h.include?('id') && h.include?('label') }
ids = items_with_label.map { |h| h['id'] }
ids
end
ids_with_label(your_json_string) # => [0, 46]

I do not know if this is what you want exactly:
items = my_parse['menu']['items'].compact.select { |item| item.has_key?('label') }.map{ |item| item['id'] }

There's no need to create a temporary array:
my_parse['menu']['items'].each_with_object([]) { |o,a|
a << o['id'] if o && o.key?('id') && o.key?('label') }
#=> [0, 46]

Related

how can I iterate through this array of hashes to receive a particular value : RUBY

the hash I have is the following:
aoh=[
{ "name": "Vesper",
"glass": "martini",
"category": "Before Dinner Cocktail",
"ingredients": [
{ "unit": "cl",
"amount": 6,
"ingredient": "Gin" },
{ "unit": "cl",
"amount": 1.5,
"ingredient": "Vodka" },
{ "unit": "cl",
"amount": 0.75,
"ingredient": "Lillet Blonde" }
],
"garnish": "Lemon twist",
"preparation": "Shake and strain into a chilled cocktail glass." },
{ "name": "Bacardi",
"glass": "martini",
"category": "Before Dinner Cocktail",
"ingredients": [
{ "unit": "cl",
"amount": 4.5,
"ingredient": "White rum",
"label": "Bacardi White Rum" },
{ "unit": "cl",
"amount": 2,
"ingredient": "Lime juice" },
{ "unit": "cl",
"amount": 1,
"ingredient": "Syrup",
"label": "Grenadine" }
],
"preparation": "Shake with ice cubes. Strain into chilled cocktail glass." }]
How can I iterate through this to get JUST the ingredient (without returning name,glass,category,etc.)? I also need the same iteration for amount but I assume that will look just like the iteration for ingredient. Sorry for the dumb question, I'm new to ruby and have attempted this for hours now.
You have an array of two elements in your example. Those two elements are hashes with key/value pairs. You can loop through the array with the #each method and access the values that the :"ingredients" keys store like this:
aoh.each do |hash|
hash[:ingredients]
end
The :ingredients keys each store another array of hashes. An example hash is:
{ "unit": "cl",
"amount": 6,
"ingredient": "Gin" }
You can then access the value under the :ingredient key by doing hash[:ingredient]. The final result looks something like this:
aoh.each do |array_element|
array_element[:ingredients].each do |ingredient|
ingredient[:ingredient]
end
end
This currently only iterates through the arrays and hashes. If you want to also print the result you can do this:
aoh.each do |array_element|
array_element[:ingredients].each do |ingredient|
puts ingredient[:ingredient]
end
end
#=> Gin
# Vodka
# Lillet Blonde
# White rum
# Lime juice
# Syrup
If you want to get a modified array, you can use #map (or #flat_map). You can also get the amount with the value like this:
aoh.flat_map do |array_element|
array_element[:ingredients].map do |ingredient|
[[ingredient[:ingredient], ingredient[:amount]]
end
end
#=> [["Gin", 6], ["Vodka", 1.5], ["Lillet Blonde", 0.75], ["White rum", 4.5], ["Lime juice", 2], ["Syrup", 1]]
>aoh.collect { |i| i[:ingredients].collect { |g| puts g[:ingredient] } }
Gin
Vodka
Lillet Blonde
White rum
Lime juice
Syrup
I would suggest the following.
aoh=[
{ "name": "Vesper",
"ingredients": [
{ "unit": "cl", "ingredient": "Gin" },
{ "unit": "cl", "ingredient": "Vodka" }
],
"garnish": "Lemon twist"
},
{ "name": "Bacardi",
"ingredients": [
{ "unit": "cl", "ingredient": "White rum" },
{ "unit": "cl", "ingredient": "Lime juice" }
],
}
]
aoh.each_with_object({}) { |g,h| h[g[:name]] =
g[:ingredients].map { |f| f[:ingredient] } }
#=> {"Vesper"=>["Gin", "Vodka"], "Bacardi"=>["White rum", "Lime juice"]}

Insert new key value into nested hash in Ruby

I am trying to insert a new key/value { type: 'Profile'} into the photo hash that has id = 111.
photo_hash = [{"id": "111","photo": "http://photo.com/111.jpeg"}, {"id": "222", "photo": "http://photo.com/222.jpeg"}]
So the final result should look like:
photo_hash = [{"id": "111","photo": "http://photo.com/111.jpeg", "type" : "Profile"}, {"id": "222", "photo": "http://photo.com/222.jpeg"}]
I feel like this should be pretty straight forward in Ruby but I am very stuck
The question suggests to me that the element h (a hash) of photos for which h[:id] = "111 (if there is one), is to be modified in place by adding the key-value pair :type=>"Profile".
photos = [{"id": "111", "photo": "http://photo.com/111.jpeg"},
{"id": "222", "photo": "http://photo.com/222.jpeg"}]
insert_item = { "type": "Profile" }
h = photos.find { |h| h[:id] == "111" }
h.update(insert_item) unless h.nil?
photos
#=> [{:id=>"111", :photo=>"http://photo.com/111.jpeg", :type=>"Profile"},
# {:id=>"222", :photo=>"http://photo.com/222.jpeg"}]
See Hash#update (aka merge!).
You need to construct a new array from your existing:
photos = [{"id": "111","photo": "http://photo.com/111.jpeg"}, {"id": "222", "photo": "http://photo.com/222.jpeg"}]
new_photos = photos.map do |photo|
if photo[:id] == '111'
photo.merge(type: 'Profile')
else
photo
end
end
You rly dont need to find object or create new Array.
Hash it self its object and can be updated.
photos = [{"id": "111","photo": "http://photo.com/111.jpeg"}, {"id": "222", "photo": "http://photo.com/222.jpeg"}]
photos.each do |hsh|
if hsh[:id] == '111'
hsh[:type] = 'Profile'
break
end
end
photos
# => [{:id=>"111", :photo=>"http://photo.com/111.jpeg", :type=>"Profile"}, {:id=>"222", :photo=>"http://photo.com/222.jpeg"}]
If you need to update multiple values from different objects with different ids you could also build a lookup tree. (Assuming there are no duplicate ids.)
photos = [{"id": "111","photo": "http://photo.com/111.jpeg"}, {"id": "222", "photo": "http://photo.com/222.jpeg"}]
photo_lookup = photos.map { |photo| [photo[:id], photo] }.to_h
#=> {
# "111" => {"id": "111","photo": "http://photo.com/111.jpeg"},
# "222" => {"id": "222", "photo": "http://photo.com/222.jpeg"}
# }
Then simply fetch the photo and update the value.
photo = photo_lookup["111"] and photo.update(type: 'Profile')
# ^ only update photo if it is found
This solution mutates the hash, meaning the hash in photos should also be updated.

hash remove item key and value

I have the following hash:
{
"itens":
[
{"year": "2018", "data": "id": 1},
{"year": "2018", "data": "id": 2}
]
}
I need to insert another element to the array of hashes. But I can't have a duplicate entry.
So I have to insert this line:
{"year": "2019", "data": "id": 2}
But first I need to remove the previous:
{"year": "2018", "data": "id": 2}
How can I iterate over the hash, find key and value, remove and add the new entry ?
Thanks.
I assume you mean a hash like this:
hash = {
items: [
{year: 2018, data: { id: 1 }},
{year: 2018, data: { id: 2 }}
]
}
Than you can change it for example in this way:
item = {year: 2019, data: { id: 2 }}
hash[:items].delete_if do |stored_hash|
stored_hash[:data][:id] == item[:data][:id]
end
hash[:items] << item
Than it would result in this:
hash
=> {:items=>[{:year=>2018, :data=>{:id=>1}}, {:year=>2019, :data=>{:id=>2}}]}
I hope this answers your question...
Given the variables:
myhash = {
"itens":
[
{"year": "2018", "data": nil, "id": 1},
{"year": "2018", "data": nil, "id": 2}
]
}
insert = {"year": "2019", "data": "something", "id": 2}
remove = {"year": "2018", "data": nil, "id": 2}
If insert and remove have the same keys, you can do:
myhash[:'itens'].find { |h| h[:'id'] == insert[:'id'] }.merge! insert
#=> {:itens=>[{:year=>"2018", :data=>nil, :id=>1}, {:year=>"2019", :data=>"something", :id=>2}]}

Convert csv to json in ruby

CSV
id,modifier1_name,modifier2_price,modifier2_name,modifier2_price,modifier2_status
1,'Small',10,'Large',20,'YYY'
2,'Small',20,'Large',30,'YYY'
JSON
[
{
id: 1,
modifier: [
{name: 'Small', price: 10},
{name: 'Large', price: 20, status: 'YYY'}]
},
{
id: 2,
modifier: [
{name: 'Small', price: 20},
{name: 'Large', price: 30, status: 'YYY'}],
}
]
How to convert CSV to Json in this case when modifiers can be different ?
You will need to map the modifiers yourself, as there is no built-in method to map hash values into an array from your logic:
JSON.pretty_generate(CSV.open('filename.csv', headers: true).map do |row|
modifier = {}
row.each do |k, v|
if k =~ /modifier(.)_(.*)$/
(modifier[$1] ||= {})[$2] = v
end
end
{ id: row['id'],
modifier: modifier.sort_by { |k, v| k }.map {|k, v| v }
}
end)
For the file*
id,modifier1_name,modifier1_price,modifier2_name,modifier2_price,modifier2_status
1,Small,10,Large,20,YYY
2,Small,20,Large,30,YYY
*I made some changes to the file you show, since it will not give you the required result - you state modifier2_price twice, for example
You will get:
[
{
"id": "1",
"modifier": [
{
"name": "Small",
"price": "10"
},
{
"name": "Large",
"price": "20",
"status": "YYY"
}
]
},
{
"id": "2",
"modifier": [
{
"name": "Small",
"price": "20"
},
{
"name": "Large",
"price": "30",
"status": "YYY"
}
]
}
]
require 'csv'
require 'json'
CSV.open('filename.csv', :headers => true).map { |x| x.to_h }.to_json

Merging records in JSON with Ruby

I have two json files that I'm trying to merge. The JSONs have different formatting (see below). I'd like to merge records, so [0] from file one and [0] from file two would become one record [0] in the new merged file.
The first JSON (file_a.json), appears like so:
{
"query": {
"count": 4,
"created": "2012-11-21T23:07:00Z",
"lang": "en-US",
"results": {
"quote": [
{
"Name": "Bill",
"Age": "46",
"Number": "3.55"
},
{
"Name": "Jane",
"Age": "33",
"Number": nil
},
{
"Name": "Jack",
"Age": "55",
"Number": nil
},
{
"Name": "Xavier",
"Age": nil,
"Number": "153353535"
}
]
}
}
}
The second JSON (file_b.json) appears like so:
[
{
"Number2": 25253,
"Number3": 435574,
"NAME": "Bill"
},
{
"Number2": 345353,
"Number3": 5566,
"NAME": "Jane"
},
{
"Number2": 56756,
"Number3": 232435,
"NAME": "Jack"
},
{
"Number2": 7457,
"Number3": 45425,
"NAME": "Xavier"
}
]
None of the keys are the same in both JSONs (well, actually "Name" is a key in both, but in the first the key is "Name" and in the second its "NAME" - just so I can check that the merge works correctly - so I want "Name" and "NAME" in the final JSON), the first record in the first file matches with the first record in the second file, and so on.
So far, I tried merging like this:
merged = %w[a b].inject([]) { |m,f| m << JSON.parse(File.read("file_#{f}.json")) }.flatten
But this of course merged them, but not how I wanted them merged (they are merged sucessively, and because of the different formatting, it gets quite ugly).
I also tried merging like this:
a = JSON.parse(File.read("file_a.json"))
b = JSON.parse(File.read("file_b.json"))
merged = a.zip(b)
Came closer but still not correct and the formatting was still horrendous.
In the end, what I want is this (formatting of second JSON - headers from first JSON can be junked):
[
{
"Name": "Bill",
"Age": 46,
"Number": 3.55,
"Number2": 25253,
"Number3": 435574,
"NAME": "Bill"
},
{
"Name": "Jane",
"Age": 33,
"Number": nil,
"Number2": 345353,
"Number3": 5566,
"NAME": "Jane"
},
{
"Name": "Jack",
"Age": 55,
"Number": nil,
"Number2": 56756,
"Number3": 232435,
"NAME": "Jack"
},
{
"Name": "Xavier",
"Age": nil,
"Number": 153353535,
"Number2": 7457,
"Number3": 45425,
"NAME": "Xavier"
}
]
Any help is appreciated. Thanks a lot.
Hеllo, seems format changed from last time :)
UPDATE: more readable version that also convert corresponding values to integers/floats:
require 'json'
require 'ap'
a = JSON.parse(File.read('./a.json'))['query']['results']['quote'] rescue []
b = JSON.parse(File.read('./b.json'))
final = []
a.each_with_index do |ah,i|
unless bh = b[i]
bh = {}
puts "seems b has no #{i} key, merging skipped"
end
final << ah.merge(bh).inject({}) do |f, (k,v)|
if v.is_a?(String)
if v =~ /\A\d+\.\d+\Z/
v = v.to_f
elsif v =~ /\A\d+\Z/
v = v.to_i
end
end
f.update k => v
end
end
ap final
will display:
[
[0] {
"Name" => "Bill",
"Age" => 46,
"Number" => 3.55,
"Number2" => 25253,
"Number3" => 435574,
"NAME" => "Bill"
},
[1] {
"Name" => "Jane",
"Age" => 33,
"Number" => nil,
"Number2" => 345353,
"Number3" => 5566,
"NAME" => "Jane"
},
[2] {
"Name" => "Jack",
"Age" => 55,
"Number" => nil,
"Number2" => 56756,
"Number3" => 232435,
"NAME" => "Jack"
},
[3] {
"Name" => "Xavier",
"Age" => nil,
"Number" => 153353535,
"Number2" => 7457,
"Number3" => 45425,
"NAME" => "Xavier"
}
]
Here is a working demo
Btw, your json is a bit wrong in both files.
See the fixed versions here and here

Resources