how to generate a Json object from string in ruby - ruby-on-rails

I have an array of strings of this format ['config = 3', 'config_b.root.a.b.c = 13'] ;
my goal is to create the following json object from them
{
"config": 3,
"config_b": {
"root": {
"a": {
"b": {
"c": 13
}
}
}
}
}
this is my current working approach
# inputs is an array of strings
def create_config(inputs)
hash={}
inputs.each do |x|
value = x.split("=")[1]
keys = x.split("=")[0].strip.split(".")
add_item(keys,value,hash)
end
print hash
end
# recusive function for adding items
def add_item(keys,value,hash)
current = keys.shift
if keys.empty?
hash[current] = value
else
hash[current] = {}
add_item(keys,value,hash[current])
end
end
I would like to know if anyone has a better approach for solving this, Thanks

I think I have a solution.
def create_config(inputs)
inputs.map do |e|
keys, value = e.split(' = ')
keys.split('.').reverse.inject(value) { |assigned_value, key| { key => assigned_value } }
end.reduce(:merge)
end
I tried it with
['config = 3', 'config_b.root.a.b.c = 13']
and got
{"config"=>"3", "config_b"=>{"root"=>{"a"=>{"b"=>{"c"=>"13"}}}}}

Related

add contents of 1 hash into another

I have a parent hash which changes and I want to ensure that the child hashes take these changes but also retain keys that they had before and those should not be lost
These are the sample hashes that I have
one = {"endpoints"=>["get_route"], "features"=>["channel_selection"], "get_route"=>{"output"=>["total_length", "seca_length"], "options"=>["panama", "abcd"]}}
other = {"endpoints"=>["get_route"], "features"=>["channel_selection"], "get_route"=>{"output"=>["total_length", "seca_length"], "options"=>["panama", "suez", "kiel"]}}
I want the other hash to now look like
other = {"endpoints"=>["get_route"], "features"=>["channel_selection"], "get_route"=>{"output"=>["total_length", "seca_length"], "options"=>["panama", "abcd", suez", "kiel"]}}
I have tried the following code but it is not working
result = propogate_changes(one, other)
def propogate_changes(one, other)
one_keys = one.keys
other_keys = other.keys
combined = Hash.new
unique_keys = one_keys.concat(other_keys).uniq
unique_keys.each do |key|
if(one[key].is_a?(Array)) then
# if(other[key] == nil) then
# combined[key] = one[key]
# else
combined[key] = one[key].concat(other[key]).uniq
# end
else
combined[key] = add_allowance(one[key], other[key])
end
end
return combined
end
The above code fails when a key is present in one but missing in another
I also tried merge, deep_merge, reverse_merge but they all overwrite my other hash with one hash but none of them retain the original data.
Any advise on this will be appreciated
Try this custom merge logic.
def find_missing_items_in_arr(arr1, arr2)
arr1_size = arr1.size
arr2_size = arr2.size
if (arr1_size == arr2_size) && (arr1 & arr2).size == arr1_size
return [] # Same array
end
arr2 - arr1
end
def custom_merge(target_hash, source_hash)
# If you want to preserve frozen state of entries, please use `clone`
duped_target_hash = target_hash.dup
source_hash.each do |k, v|
unless duped_target_hash.key?(k)
duped_target_hash[k] = v
next
end
case v
when Array
missing_items_in_arr = find_missing_items_in_arr(duped_target_hash[k], v)
if missing_items_in_arr.size > 0
duped_target_hash[k] += missing_items_in_arr
end
when Hash
duped_target_hash[k] = custom_merge(duped_target_hash[k], v)
else
# Nothing to do here
end
end
duped_target_hash
end
Usage
one = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "abcd"]
}
}
other = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "suez", "kiel"]
}
}
rs_hash = custom_merge(other, one)
puts rs_hash
Note: Rails provides a deep_merge but this can be used outside Rails. I have tested and it returns your desired output. Also it handles more nested entries like
one = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "abcd"],
"custom_options" => {
"custom_output" => ["abc"],
"custom_input" => ["xyz" ]
}
}
}
other = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "suez", "kiel"],
"custom_options" => {
"custom_output" => ["abc", "def"]
}
}
}
Hope this helps.

Grouping the array of codes

I have two different types of codes.
AllCodes = [
{
group_name: 'Marked',
group_codes: [
{
code: '1A',
description: 'Marked.'
}
],
.. //next group codes.
}
]
AllCodes = [
{
code: '2',
description: 'Located. Facilities Marked.'
}
.. //next codes.
]
I need to form an array of this format.
[
{
code: '1A',
description: 'example'
},
.. // next code
]
I did it this way, but I do not really like this approach, how can I dry up the code?
def up
Account.all.each do |account|
arrayed_codes = []
account.one_call_center.response_codes_repository_class::AllCodes.collect do |codes|
if response_code[:group_codes]
response_code[:group_codes].each do |group_codes|
arrayed_codes << {
code: group_codes[:code],
description: group_codes[:description]
}
end
else
arrayed_codes << {
code: response_code[:code],
description: response_code[:description]
}
end
end
arrayed_codes.each do |res_code|
ResponseCode.create!(account: account,
one_call_center_id: account.one_call_center.id,
close_code: res_code[:code],
description: res_code[:description],
ticket_types: account.one_call_center.ticket_types.keys)
end
end
end
# obj is an each element of your AllCodes array
codes = AllCodes.inject([]) do |codes_array, obj|
if obj.has_key?(:group_codes)
codes_array += obj[:group_codes]
else
codes_array << obj
end
end
codes_array is an injected array. Iterating over your AllCodes, if current object has_key?(:group_codes), we should take obj[:group_codes] (because it's already an array of needed format), so we merge our codes_array with it: codes_array += obj[:group_codes]. If it doesn't have that key, than it's already hash of needed format. So we just add this element to the array.

transform_keys for an array of hashes

I have the following array of hashes and I want to use transform_keys to strip the beginning of each key using a regex:
array_of_hashes = [{"a_0_abc"=>"1",
"a_0_def"=>"1",
"a_0_hij"=>"1",},
{"a_1_abc”=>"2",
"a_1_def"=>"2",
"a_1_hij"=>"2"}]
and I want the following:
transformed_hash_keys = [{"abc"=>"1",
"def"=>"1",
"hij"=>"1",},
{"abc"=>"2",
"def"=>"2",
"hij"=>"2"}]
I have the following method but it results in array_of_hashes instead of transformed_hash_keys:
def strip
s = array_of_hashes.each { |hash| hash.transform_keys { |key| key.sub(/^a_(\d+)_/, '') } }
end
Can anyone tell me what I'm doing wrong in this method?
transform_keys doesn't operate in place and each returns the original iterator, not the result of the block.
You could do what you want with map instead of each.
def strip
s = array_of_hashes.map { |hash| hash.transform_keys { |key| key.sub(/^a_(\d+)_/, '') } }
end
Or, you could use transform_keys! to modify the contents of array_of_hashes
def strip
s = array_of_hashes.each { |hash| hash.transform_keys! { |key| key.sub(/^a_(\d+)_/, '') } }
end
Here's a pure Ruby solution.
arr = [{ "a_0_abc"=>"1", "a_0_def"=>"1", "a_0_hij"=>"1" },
{ "a_1_abc"=>"2", "a_1_def"=>"2", "a_1_hij"=>"2" }]
arr.map { |h| h.map { |k,v| [k[/[[:alpha:]]+\z/], v] }.to_h }
#=> [{"abc"=>"1", "def"=>"1", "hij"=>"1"}, {"abc"=>"2", "def"=>"2", "hij"=>"2"}]
or
arr.map { |h| h.each_with_object({}) { |(k,v),g| g[k[/[[:alpha:]]+\z/]] = v } }
# => [{"abc"=>"1", "def"=>"1", "hij"=>"1"}, {"abc"=>"2", "def"=>"2", "hij"=>"2"}]
This lets you do any kind of transformation on the keys just passing a block to it:
def strip_keys(object)
deep_transform_keys_in_object!(object) { |key| key.sub(/^a_(\d+)_/, '') }
end
def deep_transform_keys_in_object!(object, &block)
case object
when Hash
object.keys.each do |key|
value = object.delete(key)
object[yield(key)] = deep_transform_keys_in_object!(value, &block)
end
object
when Array
object.map! { |e| deep_transform_keys_in_object!(e, &block) }
else
object
end
end

Rails build JSON object from hash and arrays via model

I am trying to post data to an API, the API is requesting a JSON request as this:
{
"blacklists": {
"list1": [{
"id": "123",
"find": "foo",
"type": "text"
}],
"list2": [{
"id": "321",
"find": "bar",
"type": "text"
}]
}
}
My problem is building a JSON with hash and arrays representing the above.
This is the code I have so far:
#blacklist = {}
#bl = {}
key_category = KeywordCategory.where("global_list = ? OR unit_id = ?",1,1)
key_category.each do |kc|
bl_name = kc.name # <- "list1, list2 etc."
kc.keywords.each do |kw|
#keywords = {}
#keywords['id'] = kw.id
#keywords['find'] = kw.data
#keywords['type'] = kw.is_regexp ? "regexp" : "text"
#bl.merge!(#keywords)
end
end
#blacklist['blacklist'] = #bl
return #blacklist.to_json
This code gives me this JSON (which is a bit from the one the API wants):
{
"blacklists":
{"id":123,
"find":"foo",
"type":"text"
}
}
How do I change my code so it spits out according to the top JSON?
Try something like this:
#blacklist = {}
#bl = {}
key_category = KeywordCategory.where("global_list = ? OR unit_id = ?",1,1)
key_category.each do |kc|
tmparray = []
kc.keywords.each do |kw|
#keyword = { id: kw.id, find: kw.data, type: kw.is_regexp ? 'regexp' : 'text" }
tmparray << #keyword
end
#bl.merge!(kc.name: tmparray)
end
#blacklist['blacklist'] = #bl
return #blacklist.to_json

Ruby Array of Arrays returning query to be used for chartjs,

def self.return_this_data_for_map_method
data = { :labels => [], datasets: [data: []] }
dictionary = {}
results.each do |teams|
team = teams[0]
teamMembers = teams[1]
if dictionary[team].nil?
dictionary[team] = teamMembers
else
dictionary[team] += teamMembers
end
end
data[:labels] << dictionary.keys
data[:datasets][0][:data] << dictionary.values
data
end
This is the data I am getting out
=> {:labels=>[["CUBS", "CARDS", "ROCKIES", "ASTROS"]]:datasets=>[{:data=>[[72, 93, 74, 28]]}]}
This is how I am trying to get my data
=> {:labels=>["CUBS", "CARDS", "ROCKIES", "ASTROS"], :datasets=>[{:data=>[72, 93, 74, 28]}]}
It's wrapping it like it's still in an Array of Arrays, I am not quite seeing how to break how of it. Any suggestions on how I can fix my code would be appreciated.
using ruby 2.3.1
The simplest solution is to flatten the arrays prior to returning the data:
data = { :labels => [], datasets: [data: []] }
dictionary = {}
results.each do |teams|
team = teams[0]
teamMembers = teams[1]
if dictionary[team].nil?
dictionary[team] = teamMembers
else
dictionary[team] += teamMembers
end
end
data[:labels] << dictionary.keys
data[:datasets][0][:data] << dictionary.values
data[:labels].flatten!
data[:datasets][0][:data].flatten!
data
#maxpleaner and #rails_id were correct
def self.return_this_data_for_map_method
data = { :labels => [], datasets: [data: []] }
dictionary = {}
results.each do |teams|
team = teams[0]
teamMembers = teams[1]
if dictionary[team].nil?
dictionary[team] = teamMembers
else
dictionary[team] += teamMembers
end
end
data[:labels] += dictionary.keys
data[:datasets][0][:data] += dictionary.values
data
end

Resources