Nested json from ruby hash - ruby

Trying to build a json out of Ruby hash
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
rides.each do |key, value|
temp_rides["rides"].push({"key" => "value"})
puts temp_rides
end
apparently it fails with undefined methodpush'` I believe I need to load the json object before append (based on my python background).
I am looking for a output something like this
{
"rides": {
"lyft": {
"type": "car"
},
"scoot": {
"type": "scooter"
},
"blade": {
"type": "helicopter"
}
}
}

This is an answer to the OP's original question. I have no interest in changing it to attempt to answer a moving target.
Code
def doit(rides)
{ rides: rides.slice_before { |k,_| k.to_s.match? /\Aride\d*\z/ }.
map { |a| hashify(a.flatten.drop(1)) }.
reduce(&:merge) }
end
def hashify(arr)
f,*rest = arr
return f if rest.empty?
{ f.to_sym=>hashify(rest) }
end
Examples
Here is an example of the use of the (recursive) helper method hashify:
hashify ["lyft", :type, "car"]
#=> {:lyft=>{:type=>"car"}}
We are given the hash rides:
rides = {:ride1=>"lyft", :type=>"car", :ride2=>"Scoot",
:type2=>"scooter", :ride3=>"blade", :type3=>"helicopter"}
doit rides
#=> {:rides=>{:lyft=>{:type=>"car"},
# :Scoot=>{:type2=>"scooter"},
# :blade=>{:type3=>"helicopter"}}}
Let's add some more key-value pairs to rides:
rides = {:ride1=>"lyft", :type=>"car", :color=>"blue",
:ride2=>"Scoot", :type2=>"scooter", :make=>"Vespa", :model=>"98",
:ride3=>"blade", :type3=>"helicopter"}
doit rides
#=> {:rides=>{:lyft=>{:type=>{:car=>{:color=>"blue"}}},
# :Scoot=>{:type2=>{:scooter=>{:make=>
# {:Vespa=>{:model=>"98"}}}}},
# :blade=>{:type3=>"helicopter"}}}
Explanation
The steps for the first example are as follows.
enum = rides.slice_before { |k,_| k.to_s.match? /\Aride\d*\z/ }
#=> #<Enumerator: #<Enumerator::Generator:0x00005a49d68217f0>:each>
We can see the elements that will be generated by this enumerator by converting it to an array.
enum.to_a
#=> [[[:ride1, "lyft"], [:type, "car"]],
# [[:ride2, "Scoot"], [:type2, "scooter"]],
# [[:ride3, "blade"], [:type3, "helicopter"]]]
Continuing,
a = enum.map { |a| hashify(a.flatten.drop(1)) }
#=> [{:lyft=>{:type=>"car"}},
# {:Scoot=>{:type2=>"scooter"}},
# {:blade=>{:type3=>"helicopter"}}]
h = a.reduce(&:merge)
#=> {:lyft=>{:type=>"car"}, :Scoot=>{:type2=>"scooter"},
# :blade=>{:type3=>"helicopter"}}
{ rides: h }
#=> <as above>

It gave you undefined method push because there is no such method for a hash.
temp_rides = {"rides": {}}
# This create a hash with `:rides` symbol as the key {:rides=>{}}
# To push an object into a hash. Use operator[]= or #store method
temp_rides[:rides][:key1] = 'value1'
temp_rides[:rides].store(:key2, 'value2')
A working example:
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
rides.each_pair do |k, v|
temp_rides[:rides][k] = {:type => v}
end
puts JSON.pretty_generate(temp_rides)

This is actually pretty easy to do with a simple transform:
def rejig(rides)
rides.map do |name, type|
[ name, { type: type } ]
end.to_h
end
Where that will rework your structure into the desired shape.
Using it is easy:
require 'json'
rides = { lyft: "car", scoot: "scooter", blade: "helicopter" }
puts JSON.dump(rejig(rides))
# => {"lyft":{"type":"car"},"scoot":{"type":"scooter"},"blade":{"type":"helicopter"}}

require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
temp_rides[:rides].merge!(rides)
to get ruby hash output
temp_rides
to get json format output
JSON.dump temp_rides

require 'json'
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
transformed = {
'rides' => rides.inject({}) { |h,(k,v)| h[k] = { 'type' => v }; h }
}
JSON.dump(transformed)

Related

Ruby: transform Hash-Keys

I have a Hash:
urls = [{'logs' => 'foo'},{'notifications' => 'bar'}]
The goal is to add a prefix to the keys:
urls = [{'example.com/logs' => 'foo'},{'example.com/notifications' => 'bar'}]
My attempt:
urls.map {|e| e.keys.map { |k| "example.com#{k}" }}
Then I get an array with the desired form of the keys but how can I manipulate the original hash?
If you want to "manually" transform the keys, then you can first iterate over your array of hashes, and then over each object (each hash) map their value to a hash where the key is interpolated with "example.com/", and the value remains the same:
urls.flat_map { |hash| hash.map { |key, value| { "example.com/#{key}" => value } } }
# [{"example.com/logs"=>"foo"}, {"example.com/notifications"=>"bar"}]
Notice urls are being "flat-mapped", otherwise you'd get an arrays of arrays containing hash/es.
If you prefer to simplify that, you can use the built-in method for for transforming the keys in a hash that Ruby has; Hash#transform_keys:
urls.map { |url| url.transform_keys { |key| "example.com/#{key}" } }
# [{"example.com/logs"=>"foo"}, {"example.com/notifications"=>"bar"}]
Use transform_keys.
urls = [{'logs' => 'foo'}, {'notifications' => 'bar'}]
urls.map { |hash| hash.transform_keys { |key| "example.com/#{key}" } }
# => [{"example.com/logs"=>"foo"}, {"example.com/notifications"=>"bar"}]
One question: are you best served with an array of hashes here, or would a single hash suit better? For example:
urls = { 'logs' => 'foo', 'notifications' => 'bar' }
Seems a little more sensible a way to store the data. Then, saying you did still need to transform these:
urls.transform_keys { |key| "example.com/#{key}" }
# => {"example.com/logs"=>"foo", "example.com/notifications"=>"bar"}
Or to get from your original array to the hash output:
urls = [{'logs' => 'foo'}, {'notifications' => 'bar'}]
urls.reduce({}, &:merge).transform_keys { |key| "example.com/#{key}" }
# => {"example.com/logs"=>"foo", "example.com/notifications"=>"bar"}
Much easier to work with IMHO :)
If you don't have access to Hash#transform_keys i.e. Ruby < 2.5.5 this should work:
urls.map{ |h| a = h.to_a; { 'example.com/' + a[0][0] => a[0][1] } }

How to loop inside a hash?

If I have a hash like the one below, and I want to loop over the second level keys.
Why does this fail?
hash["Element"].each do |id|
h[id] = hash[id]["Name"]
end
hash
{
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
It fails because using .each on a Hash yields the tuple of key and value.
hash = {
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
}
hash["Element"].each do |id|
p id
end
["499723", {"Name"=>"A"}]
["499725", {"Name"=>"B"}]
Therefore, you need to use
hash["Element"].each do |id, value|
# ...
end
If you don't need the value
hash["Element"].each do |id, _|
# ...
end
However, you can keep the value and access it directly
hash["Element"].each do |id, value|
h[id] = value["Name"]
end
A simple solution in your case is to use Enumberable#each_with_object in combination with the previous information:
hash["Element"].each_with_object({}) do |(id, value), acc|
acc[id] = value["Name"]
end
# => {"499723"=>"A", "499725"=>"B"}
hash["Element"].each.with_object({}) do |(id, subhash), result|
result[id] = subhash["Name"]
end

Merge an array of hashes that have the same key but different value and add the value

Hello i have a hash that loos similar to this
#receivers
=> [{:amount=>50, :email=>"user_02#example.com"},
{:amount=>50, :email=>"user_02#example.com"},
{:amount=>50, :email=>"user_02#example.com"},
{:amount=>100, :email=>"user_01#example.com"},
{:amount=>100, :email=>"user_01#example.com"}]
How do i make it look like this?:
#receivers
=> [{:amount=>150, :email=>"user_02#example.com"}
{:amount=>200, :email=>"user_01#example.com"}]
Thank you for your help.
You can calculate it like this:
#receivers.group_by { |e| e[:email] }
.map { |k, v| { amount: v.sum { |e| e[:amount] }, email: k } }
#=> [{:amount=>150, :email=>"user_02#example.com"},
# {:amount=>200, :email=>"user_01#example.com"}]

parse json to object ruby

I looked into different resources and still get confused on how to parse a json format to a custom object, for example
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
and JSON file
{
"Resident": [
{
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}
]
}
what's the correct way to parse the json file into a array of 3 Resident object?
Today i was looking for something that converts json to an object, and this works like a charm:
person = JSON.parse(json_string, object_class: OpenStruct)
This way you could do person.education.school or person[0].education.school if the response is an array
I'm leaving it here because might be useful for someone
The following code is more simple:
require 'json'
data = JSON.parse(json_data)
residents = data['Resident'].map { |rd| Resident.new(rd['phone'], rd['addr']) }
If you're using ActiveModel::Serializers::JSON you can just call from_json(json) and your object will be mapped with those values.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name, :age, :awesome
def attributes=(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
def attributes
instance_values
end
end
json = {name: 'bob', age: 22, awesome: true}.to_json
person = Person.new
person.from_json(json) # => #<Person:0x007fec5e7a0088 #age=22, #awesome=true, #name="bob">
person.name # => "bob"
person.age # => 22
person.awesome # => true
require 'json'
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
s = '{"Resident":[{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"}]}'
j = JSON.parse(s)
objects = j['Resident'].inject([]) { |o,d| o << Resident.new( d['phone'], d['addr'] ) }
p objects[0].phone
"12345"
We recently released a Ruby library static_struct that solves the issue. Check it out.

Link_to with additional variable

I want to create a simple link_to (rails 3) with two additional variables:
= link_to 'Try', new_try_path(:k => users.collect{|m| m.user.username}, :h=> users2.collect{|m| m.user2.username2}, :proof => true)
The problem is if users2 is blank, this html code is generated: &k=[1]&&proof=true
I tried something like this. Can you help me please?
= link_to 'Try', new_try_path(:k => users.collect{|m| m.user.username}, :h=> users2.collect{|m| m.user2.username2} if users2.blank?, :proof => true)
Thank you!
Things like this should definitely be refactored into a helper, such as
# view
= try_link(users, users2)
# helper
def try_link(users, users2)
options = { :k => users.collect { |m| m.user.username }, :proof => true }
unless users2.blank?
options[:h] = users2.collect { |m| m.user2.username2 }
end
link_to 'Try', new_try_path(options)
end
This is about the bare minimum you can do to make the view code less horrible.
You might also want to consider putting the whole collect thing into the model.
Also Hash#merge might be helpful in cases like this, where you can do
a = { :foo => 1 }
b = { :bar => 2 }
puts a.merge(b) # => { :foo => 1, :bar => 2 }
Not very elegant, but should work:
- options = { :k => users.map{ |m| m.user.username }, :proof => true }
-# add :h parameter only if users2 is not empty
- options[:h] = users2.map{ |m| m.user2.username2 } unless users2.blank?
= link_to 'Try, new_try_path(options)
If users2 is blank h parameter will be omitted from generated URL.
As alternative you can filter out blank values from options hash:
# for ruby 1.9 (select only non-blank values)
options.select! { |k, v| v.present? }
# for ruby 1.8 (delete blank values)
options.delete_if { |k, v| v.blank? }

Resources