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.
Related
I have two data structures that I would like to merge together - as I understand this is called a 'deep merge'.
It should follow the same logic as Rails' deep merge, except my requirement differs in that values are only overwritten on the target hash if they match certain conditions.
As an example, given the following two data structures:
hash1 = {
"data": [
{"id": "1", "type": "foo", created_at: "<IGNORE>" },
{"id": "2", "type": "bar", created_at: "<IGNORE>" }
],
meta: {
page: 1
}
}
hash2 = {
"data": [
{"id": "1", "type": "baz", created_at: "01.01.2022", name: 'thing' },
{"id": "2", "type": "qux", created_at: "02.01.2022" }
],
meta: {
page: 1
}
}
I would like to achieve the following output:
irb(main):001:0> hash1.deep_merge(hash2)
=> {
"data": [
{"id": "1", "type": "baz", created_at: "<IGNORE>", name: 'thing' },
{"id": "2", "type": "qux", created_at: "<IGNORE>" }
],
meta: {
page: 1
}
}
In hash1, the values for created_at contain a tag <IGNORE> that should stipulate that the corresponding value from hash2 should not be merged. All the other keys and values should merge as it would if I was to use Rails' deep_merge.
Disclaimer: You didn't say what the conditions are, even so I'll help you transform this input into this output, it would be interesting to edit your question later and say the conditions.
i don't have many knowledge in ruby but i created a simple algorithm for merge two hash structures without duplicates and keep data type of key, it's not
the better algorithm or solution but i think this implemetation help to your a find other better solution for resolver your problem
Complexity O(n)
Join the hashes and transform the keys into a string
result = hash1.merge(hash2).transform_keys(&:to_s)
save meta information
meta = hash1[:meta]
convert array of symbols to array of strings
`result = result["data"].map do |res|
{
'id' => res[:id].to_s,
'type' => res[:type].to_s,
'created_at' => res[:created_at].to_s,
'name' => res[:name].to_s
}
end
`
if the hash does not have the key name remove it
`
result =
result.map {
|res| res["name" == ""] ? {
'id' => res["id"],
'type' => res["type"],
'created_at' => res["created_at"]
} : res
}
`
Build new hash :D
final_result = {"data" => result, meta: meta}
output result
I have the below array of hashes.
I want to add a new key,value pair to "hashes" which are in "all" array. Is there any better way of looping through, than what I am doing currently?
stack = {
"all": [
"mango",
"apple",
"banana",
"grapes"
],
"mango": {
"TYPE": "test",
"MAX_SIZE": 50,
"REGION": "us-east-1"
},
"apple": {
"TYPE": "dev",
"MAX_SIZE": 55,
"REGION": "us-east-1"
},
"banana": {
"TYPE": "test",
"MAX_SIZE": 60,
"REGION": "us-east-1"
},
"grapes": {
"TYPE": "dev",
"MAX_SIZE": 80,
"REGION": "us-east-1"
},
"types": [
"dev",
"test"
]
}
My code:
stack['all'].each do |fruit|
stack[fruit].each do |fruit_name|
stack[fruit_name]['COUNT'] = stack[fruit_name]['MAX_SIZE'] * 2
end
end
Expected output:
stack = {
"all": [
"mango",
"apple",
"banana",
"grapes"
],
"mango": {
"TYPE": "test",
"MAX_SIZE": 50,
"REGION": "us-east-1",
"COUNT" : 100
},
"apple": {
"TYPE": "dev",
"MAX_SIZE": 55,
"REGION": "us-east-1",
"COUNT" : 110
},
"banana": {
"TYPE": "test",
"MAX_SIZE": 60,
"REGION": "us-east-1",
"COUNT" : 120
},
"grapes": {
"TYPE": "dev",
"MAX_SIZE": 80,
"REGION": "us-east-1",
"COUNT" : 160
},
"types": [
"dev",
"test"
]
}
There is no need for the second loop. The following does what you want:
keys = stack[:all].map(&:to_sym)
keys.each do |key|
stack[key][:COUNT] = stack[key][:MAX_SIZE] * 2
end
In the above code-block stack[:all] will return an array of keys as strings, .map(&:to_sym) will convert each string in the resulting array into a symbol.
Another way to achieve the same result would be to use either fetch_values or values_at to retrieve an array of values belonging to the provided keys. The difference being that fetch_values raises an exception if a key is missing while values_at returns nil for that key.
fruits = stack.fetch_values(*stack[:all].map(&:to_sym))
fruits.each do |fruit|
fruit[:COUNT] = fruit[:MAX_SIZE] * 2
end
If you are wondering why there is a * before stack[:all].map(&:to_sym), this is to convert the array into individual arguments. In this context * is called the spat operator.
You might write the code as follows.
stack[:all].each do |k|
h = stack[k.to_sym]
h[:COUNT] = 2*h[:MAX_SIZE] unless h.nil?
end
When, for example, `k = "mango",
h #=> h={:TYPE=>"test", :MAX_SIZE=>50, :REGION=>"us-east-1", :COUNT=>100}
I've defined the local variable h for three reasons:
it simplifies the code by avoiding multiple references to stack[k.to_sym]
when debugging it may may be helpful to be able to examine h
it makes the code more readable
Note that h merely holds an existing hash; it does not create a copy of that hash, so it has a neglibile effect on memory requirements.
The technique of defining local variables to hold objects that are parts of other objects is especially useful for more complex objects. Suppose, for example, we had the hash
hash = {
cat: { sound: "purr", lives: 9 },
dog: { sound: "woof", toys: ["ball", "rope"] }
}
Now suppose we wish to add a dog toy
new_toy = "frisbee"
if it is not already present in the array
hash[:dog][:toys]
We could write
hash[:dog][:toys] << new_toy unless hash[:dog][:toys].include?(new_toy)
#=> ["ball", "rope", "frisbee"]
hash
#=> {:cat=>{:sound=>"purr", :lives=>9},
# :dog=>{:sound=>"woof", :toys=>["ball", "rope", "frisbee"]}}
Alternatively, we could write
dog_hash = hash[:dog]
#=> {:sound=>"woof", :toys=>["ball", "rope"]}
dog_toys_arr = dog_hash[:toys]
#=> ["ball", "rope"]
dog_toys_arr << new_toy unless dog_toys_arr.include?(new_toy)
#=> ["ball", "rope", "frisbee"]
hash
#=> {:cat=>{:sound=>"purr", :lives=>9},
# :dog=>{:sound=>"woof", :toys=>["ball", "rope", "frisbee"]}}
Not only does the latter snippet display intermediate results, it probably is a wash with the first snippet in terms of execution speed and storage requirements and arguably is more readable. It also cuts down on careless mistakes such as
hash[:dog][:toys] << new_toy unless hash[:dog][:toy].include?(new_toy)
If one element of stack[:all] were, for example, "pineapple", stack[:pineapple] #=> nil since stack has no key :pineapple. If, however, stack contained the key-value pair
nil=>{ sound: "woof", toys: ["ball", "rope"] }
that would become problem. Far-fetched? Maybe, but it is perhaps good practice--in part for readability--to avoid the assumption that h[k] #=> nil means h has no key k; instead, use if h.key?(k). For example:
stack[:all].each do |k|
key = k.to_sym
if stack.key?(key)
h = stack[key]
h[:COUNT] = 2*h[:MAX_SIZE]
end
end
I am working with an API which accepts some JSON objects (sent as post request) and fails others based on certain criteria.
I am trying to compile a "log" of the objects which have failed and ones which have been validated successfully so I don't have to manually copy and paste them each time. (There are hundreds of objects).
Basically if the API returns "false", I want to push that object into a file, and if it returns true, all those objects go into another file.
I have tried to read a bunch of documentation / blogs on "select, detect, reject" etc enumerators but my problem is very different from the examples given.
I have written some pseudo code in my ruby file below and I think I'm going along the right lines, but need a bit of guidance to complete the task:
restaurants = JSON.parse File.read('pretty-minified.json')
restaurants.each do |restaurant|
create_response = HTTParty.post("https://api.hailoapp.com/business/create",
{
:body => restaurant.to_json,
:headers => { "Content-Type" => "text", "Accept" => "application/x-www-form-urlencoded", "Authorization" => "token #{api_token}" }
})
data = create_response.to_hash
alert = data["valid"]
if alert == false
# select restaurant json objects which return false and push into new file
# false_rest = restaurants.detect { |r| r == false }
File.open('false_objects.json', 'w') do |file|
file << JSON.pretty_generate(false_rest)
else
# select restaurant json objects which return true and push into another file
File.open('true_objects.json', 'w') do |file|
file << JSON.pretty_generate()
end
end
An example of the output (JSON) from the API is as follows:
{"id":"102427","valid":true}
{"valid":false}
The JSON file is basically an huge array of hashes (or objects), here is a short excerpt:
[
{
"id": "223078",
"name": "3 South Place",
"phone": "+442032151270",
"email": "3sp#southplacehotel.com",
"website": "",
"location": {
"latitude": 51.5190536,
"longitude": -0.0871038,
"address": {
"line1": "3 South Place",
"line2": "",
"line3": "",
"postcode": "EC2M 2AF",
"city": "London",
"country": "UK"
}
}
},
{
"id": "210071",
"name": "5th View Bar & Food",
"phone": "+442077347869",
"email": "waterstones.piccadilly#elior.com",
"website": "http://www.5thview.com",
"location": {
"latitude": 51.5089594,
"longitude": -0.1359897,
"address": {
"line1": "Waterstone's Piccadilly",
"line2": "203-205 Piccadilly",
"line3": "",
"postcode": "W1J 9HA",
"city": "London",
"country": "UK"
}
}
},
{
"id": "239971",
"name": "65 & King",
"phone": "+442072292233",
"email": "hello#65king.com",
"website": "http://www.65king.com/",
"location": {
"latitude": 51.5152533,
"longitude": -0.1916538,
"address": {
"line1": "65 Westbourne Grove",
"line2": "",
"line3": "",
"postcode": "W2 4UJ",
"city": "London",
"country": "UK"
}
}
}
]
Assuming you want to filter by emails, ending with elior.com (this condition might be easily changed):
NB! The data above looks like a javascript var, it’s not a valid ruby object. I assume you just got it from somewhere as a string. That’s why json:
require 'json'
array = JSON.parse(restaurants) # data is a string: '[{....... as you received it
result = array.group_by do |e|
# more sophisticated condition goes here
e['email'] =~ /elior\.com$/ ? true : false
end
File.open('false_objects.json', 'w') do |file|
file << JSON.pretty_generate(result[false])
end
File.open('true_objects.json', 'w') do |file|
file << JSON.pretty_generate(result[true])
end
There is a hash in result, containing two elements:
#⇒ {
# true: [..valids here ..],
# false: [..invalids here..]
# }
{
"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]
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