array of hashes as hash key and value as array using jbuilder - ruby-on-rails

I'm trying to generate JSON response using Jbuilder
I have an array of hashes like this
words= [
{"term": "abc",
"definition": "123"
} ,
{"term": "abc",
"definition": "345"
} ,
{"term": "xyz",
"definition": "890"
}
]
I would like covert this into JSON.
logic here is take all terms as keys and push it's definitions into arrays
{
"abc": ["123","345"],
“xyz”: ["890"]
}
What I achieved so far is
words.each do |word|
json.set! word['text'] ,word['definition']
end
gives me
{
"abc": "123"
"abc": "345",
"xyz": "890"
}
Could some help me on this.

simplest solution :)
words= [
{"term": "abc",
"definition": "123"
} ,
{"term": "abc",
"definition": "345"
} ,
{"term": "xyz",
"definition": "890"
}
]
result_hash = Hash.new{|hsh, key| hsh[key]=[] }
words.map{|x| result_hash[x[:term]].push(x[:definition])}
your output will be in result_hash

You are looking for something like this,
words = [{:term=>"abc", :definition=>"123"}, {:term=>"abc", :definition=>"345"}, {:term=>"xyz", :definition=>"890"}]
words.inject({}) do |h, w|
h[w[:term]] ||= []
h[w[:term]] << w[:definition]
h
end
#=> {"abc"=>["123", "345"], "xyz"=>["890"]}

words.group_by{|d| d[:term]}.map{|k,v| {k => v.map{|val| val[:definition]}}}.reduce(&:merge)

words.map(&:values).group_by(&:shift).each do |k, values|
json.set! k, values.flatten
end
If the order of :term and :definition is not guaranteed, there is an intermediate call to .map(&:sort) on the original hash required, and :shift should be read as :pop since after sorting :definitions will preceed :terms.

Related

How to remove multiple attributes from a json using ruby

I have a json object. It has multiple fields "passthrough_fields" which is unnecessary for me and I want to remove them. Is there a way to get all those attributes filtered out?
JSON:
{
"type": "playable_item",
"id": "p06s0lq7",
"urn": "urn:bbc:radio:episode:p06s0mk3",
"network": {
"id": "bbc_radio_five_live",
"key": "5live",
"short_title": "Radio 5 live",
"logo_url": "https://sounds.files.bbci.co.uk/v2/networks/bbc_radio_five_live/{type}_{size}.{format}",
"passthrough_fields": {}
},
"titles": {
"primary": "Replay",
"secondary": "Bill Shankly",
"tertiary": null,
"passthrough_fields": {}
},
"synopses": {
"short": "Bill Shankly with Sue MacGregor in 1979 - five years after he resigned as Liverpool boss.",
"medium": null,
"long": "Bill Shankly in conversation with Sue MacGregor in 1979, five years after he resigned as Liverpool manager.",
"passthrough_fields": {}
},
"image_url": "https://ichef.bbci.co.uk/images/ic/{recipe}/p06qbz1x.jpg",
"duration": {
"value": 1774,
"label": "29 mins",
"passthrough_fields": {}
},
"progress": null,
"container": {
"type": "series",
"id": "p06qbzmj",
"urn": "urn:bbc:radio:series:p06qbzmj",
"title": "Replay",
"synopses": {
"short": "Colin Murray unearths classic sports commentaries and interviews from the BBC archives.",
"medium": "Colin Murray looks back at 90 years of sport on the BBC by unearthing classic commentaries and interviews from the BBC archives.",
"long": null,
"passthrough_fields": {}
},
"activities": [],
"passthrough_fields": {}
},
"availability": {
"from": "2018-11-16T16:18:54Z",
"to": null,
"label": "Available for over a year",
"passthrough_fields": {}
},
"guidance": {
"competition_warning": false,
"warnings": null,
"passthrough_fields": {}
},
"activities": [],
"uris": [
{
"type": "latest",
"label": "Latest",
"uri": "/v2/programmes/playable?container=p06qbzmj&sort=sequential&type=episode",
"passthrough_fields": {}
}
],
"passthrough_fields": {}
}
Is there a way I can remove all those fields and store the updated json in a new variable?
You can do this recursively to tackle nested occurances of passthrough_fields, whether they're found in an array or a sub hash. Inline comments to explain things a little as it goes:
hash = JSON.parse(input) # convert the JSON to a hash
def remove_recursively(hash, *to_remove)
hash.each do |key, val|
hash.except!(*to_remove) # the heavy lifting: remove all keys that match `to_remove`
remove_recursively(val, *to_remove) if val.is_a? Hash # if a nested hash, run this method on it
if val.is_a? Array # if a nested array, loop through this checking for hashes to run this method on
val.each { |el| remove_recursively(el, *to_remove) if el.is_a? Hash }
end
end
end
remove_recursively(hash, 'passthrough_fields')
To demonstrate, with a simplified example:
hash = {
"test" => { "passthrough_fields" => [1, 2, 3], "wow" => '123' },
"passthrough_fields" => [4, 5, 6],
"array_values" => [{ "to_stay" => "I am", "passthrough_fields" => [7, 8, 9]}]
}
remove_recursively(hash, 'passthrough_fields')
#=> {"test"=>{"wow"=>"123"}, "array_values"=>[{"to_stay"=>"I am"}]}
remove_recursively(hash, 'passthrough_fields', 'wow', 'to_stay')
#=> {"test"=>{}, "array_values"=>[{}]}
This will tackle any arrays, and will dig for nested hashes however deep it needs to go.
It takes any number of fields to remove, in this case a single 'passthrough_fields'.
Hope this helps, let me know how you get on.
I think that the easiest solution would be to:
convert JSON into hash (JSON.parse(input))
use this answer to extend the functionality of Hash (save it in config/initializers/except_nested.rb)
on the hash from 1st step, call:
without_passthrough = your_hash.except_nested('passthrough_fields')
covert hash to JSON (without_passthrough.to_json)
Please keep in mind that it will work for passthrough_fields that is nested directly in hashes. In your JSON, you have the following part:
"uris" => [
{
"type"=>"latest",
"label"=>"Latest",
"uri"=>"/v2/programmes/playable?container=p06qbzmj&sort=sequential&type=episode",
"passthrough_fields"=>{}
}
]
In this case, the passthrough_fields will not be removed. You have to find a more sophisticated solution :)
You can do something like this:
def nested_except(hash, except_key)
sanitized_hash = {}
hash.each do |key, value|
next if key == except_key
sanitized_hash[key] = value.is_a?(Hash) ? nested_except(value, except_key) : value
end
sanitized_hash
end
json = JSON.parse(json_string)
sanitized = nested_except(json, 'passthrough_fields')
See example:
json = { :a => 1, :b => 2, :c => { :a => 1, :b => { :a => 1 } } }
nested_except(json, :a)
# => {:b=>2, :c=>{:b=>{}}}
This helper can easily be converted to support multiple keys to except, simply by except_keys = Array.wrap(except_key) and next if except_keys.include?(key)

How to take keep parts of an array and form a new array?

I am building a Rails 5 app.
In this app I have connected to the Google Calendar API.
The connection works fine and I get a list of calendars back.
What I need to do is to get the Id and Summary of this JSON object that I get back from Google.
This is what I get
[{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
},
{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
}]
This is what I want to end up with
[{
"id": "xxx.com_asae#group.calendar.google.com",
"title": "My office calendar",
}]
The purpose of this is that I want to populate a selectbox using Selectize plugin
Another way to achieve removing of certain keys in your hash is by using Hash#reject method:
response = { your_json_response }
expected = [response[0].reject {|k| k != :id && k != :summary}]
The original response remains unchanged while a mutated copy of the original response is returned.
You can filter the desierd keys with the select method:
responde = {your_json_response}
expected = [response[0].select{|k,v| ['id','title'].include?(k)}]
response[0] retrieves the hash, and the select compares each key with the ones you want and returns a hash with only those key: value pairs.
EDIT: I missed that you don't have a "title" key on the original response, I would do this then:
response = {your_json_response}
h = response[0]
expected = [{'id' => h['id'], 'title' => h['summary']}]
EDIT 2: Sorry, the first example was not clear that there would be multiple hashes
expected = response.map{|h| {'id' => h['id'], 'title' => h['summary']}}
map iterates over each element of response and returns the result of the block applied for each iteration as an array, so the blocks is apllied to each h and it generates a new hash from it
I suggest this approach.
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }
It returns just the required pairs:
# => [{:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}, {:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}]
To remove duplicates, just do expected.uniq
If you need to change the key name :summary to :title do:
expected = expected.each { |h| h[:title] = h.delete(:summary) }
One liner
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }.each { |h| h[:title] = h.delete(:summary) }.uniq
Of course, maybe it is better to move .uniq as first method expected = response.uniq.each { .....

Iterate through a hash. However, my value is changing every time

I'm currently working on a simple hash loop, to manipulate some json data. Here's my Json data:
{
"polls": [
{ "id": 1, "question": "Pensez-vous utiliser le service de cordonnerie/pressing au moins 2 fois par mois ?" },
{ "id": 2, "question": "Avez-vous passé une bonne semaine ?" },
{ "id": 3, "question": "Le saviez-vous ? Il existe une journée d'accompagnement familial." }
],
"answers": [
{ "id": 1, "poll_id": 1, "value": true },
{ "id": 2, "poll_id": 3, "value": false },
{ "id": 3, "poll_id": 2, "value": 3 }
]
}
I want to have the poll_id value and the value from the answers hash. So here's what I code :
require 'json'
file = File.read('data.json')
datas = JSON.parse(file)
result = Hash.new
datas["answers"].each do |answer|
result["polls"] = {"id" => answer["poll_id"], "value" => answer["value"]}
end
polls_json = result.to_json
However, it returns me :
{
"polls": {
"id": 2,
"value": 3
}
}
Here's the output i am looking for :
{
"polls": [
{
"id": 1,
"value": true
},
{
"id": 2,
"value": 3
},
{
"id": 3,
"value": false
}
]
}
It seems that the value is not saved into my loop. I've tried different method but I still cannot find a solution .. Any suggestions?
You should be using reduce here, i.e.
datas["answers"].reduce({ polls: [] }) do |hash, data|
hash[:polls] << { id: data["poll_id"], value: data["value"] }
hash
end
This method iterates through the answers, making available the object supplied to reduce (in this case a hash with a :polls array) to which we pass each data hash.
I'd personally, um, reduce this a little further with the following, although it's at some cost to readability:
datas["answers"].reduce({ polls: [] }) do |hash, data|
hash.tap { |h| h[:polls] << { id: data["poll_id"], value: data["value"] } }
end
It's the cleanest method to achieve what you're looking for, using a built-for-purpose method.
Docs for reduce here: https://ruby-doc.org/core-2.1.0/Enumerable.html#method-i-reduce
(I'd also be inclined to update the variable names - data is already plural, so 'datas' is a little confusing to anyone else coming to your code.)
Edit: #max makes a great point re symbol / string keys from your data - keep that in mind if you attempt to apply this.
try the below:
require 'json'
file = File.read('data.json')
datas = JSON.parse(file)
result = Hash.new
poll_json = []
datas["answers"].each do |answer|
poll_json << {"id" => answer["poll_id"], "value" => answer["value"]}
end
p "json = "#{poll_json}"
{
polls: datas["answers"].map do |a|
{ id: a["poll_id"], value: a["value"] }
end
}
In general use .map to iterate through arrays and hashes and return new objects. .each should only be used when you are only concerned about the side effects (like in a view when you are outputting values).
require 'json'
json = JSON.parse(File.read('data.json'))
result = {
polls: json["answers"].map do |a|
{ id: a["poll_id"], value: a["value"] }
end
}
puts result.to_json
The output is:
{"polls":[{"id":1,"value":true},{"id":3,"value":false},{"id":2,"value":3}]}

Filter through nested JSON object and obtain JSON with specific keys, using Ruby

I currently have a nested JSON object which resembles
{
"People": [
{
"Name": "James",
"Age": "18",
"Gender": "Male",
"Sports": []
},
{
"Name": "Sarah",
"Age": "19",
"Gender": "Female",
"Sports": [
"Soccer",
"Basketball",
"Football"
]
}
]
}
Being new to Ruby, I aim to filter throught the entire json and return only the json object/objects in which the "Sports" array has content. So in the above scenario I expect to obtain the object below as a final outcome:
{
"Name": "Sarah",
"Age": "19",
"Gender": "Female",
"Sports": [
"Soccer",
"Basketball",
"Football"
]
}
Will I have to initiate a new method to perform such an act? Or would using regular ruby calls work in this case?
Although #philipyoo answer is right, it miss an explanation on how to "filter" the parsed JSON. If you are new to ruby, take a look at Array#keep_if : http://ruby-doc.org/core-2.2.0/Array.html#method-i-keep_if
require 'json'
people = JSON.parse("{long JSON data ... }")
people_with_sports = people.fetch('People', []).keep_if do |person|
!person.fetch('Sports', []).empty?
end
If you're getting a JSON object from a request, you want to parse it and then you can traverse the hash and arrays to find the information you need. See http://ruby-doc.org/stdlib-2.0.0/libdoc/json/rdoc/JSON.html
In your case, something like this:
require 'json'
parsed_json = JSON.parse('{"People": [ ... ]}')
parsed_json["People"].each do |person|
puts person if person["name"] == "Sarah"
end

Displaying an object with Ruby on Rails

I have some JSON that looks like this. I have it stored and read into an object, #items.
[
{
{
"id": "A",
"description": "a_description"
},
{
"id": "B",
"description": "b_description"
}
},
{
{
"id": "A",
"description": "a_description"
},
{
"id": "B",
"description": "b_description"
}
}
]
My goal is to display a table with two columns, one labeled A and the other labeled B, in which each row gives the "a_description" and "b_description". I'm not sure how to go about doing this.
Ah, the ol' array of hashes and hashes of arrays problem.
To get around your "out of order" problem you first have to convert
{
"id": "A",
"description": "foo"
},
{
"id": "B",
"description": "bar"
}
into {"A" : "foo", "B" : "bar" }.
#new_items = #items.map do |item|
output = {}
item.each do |hash|
output.merge!(hash["id"] => hash["description"])
end
end
Then #new_items becomes (intentionally presented out of order since hash elements are not ordered)
[
{
"A": "a1_description",
"B": "b1_description"
},
{
"B": "b2_description",
"A": "a2_description"
}
]
From there, each line is simply a hash, so you can just dereference the value you need based on the column you're in.
#new_items.each do |item|
puts "#{item['A']} is paired with #{item['B']}"
end
Keys, of course could be retrieved dynamically if you don't want to hard code "A" and "B" using .keys
Something like this maybe
<tr><th>A</th><th>B</th></tr>
<% #items.each do |item| %>
<tr><td><%=item[0].description%></td><td><%=item[1].description%></td></tr>
<% end %>

Resources