Parameterized JSON key naming - ruby-on-rails

I have the following JSON:
{
my_json: {
id: 1,
name: "John"
}
}
How can I customize key name via parameterized like:
def jsonize(custom_key="id")
{
my_json: {
"#{custom_key}": 1,
name: "John"
}
}
end
To be output with:
Scenario 1:
=> jsonize
OUTPUT:
{
my_json: {
id: 1,
name: "John"
}
}
Scenario 2:
=> jsonize("value")
OUTPUT:
{
my_json: {
value: 1,
name: "John"
}
}

You can use ":" to separate symbolic keys and values, use "=>" in your example:
def jsonize(custom_key="id")
{
my_json: {
"#{custom_key}" => 1,
name: "John"
}
}
end

The hash-rocket syntax has been in Ruby since ancient times:
{ :foo => 1, "bar" => 2 }
Ruby 1.9 (I think) introduced a new colon shortcut syntax just for symbols (while keeping the hash-rocket general for any key type):
{ foo: 1, "bar" => 2 }
Ruby 2.2 (I think) introduced the possibility of symbolizing a string in this syntax:
{ "foo": 1, "bar" => 2 }
All of these do the same thing. What you are doing is perfectly grammatical Ruby code -- in a sufficiently new Ruby. In older Rubies, you will need to use the old reliable hash-rocket syntax:
{ "foo".to_sym => 1, "bar" => 2 }
Now that you actually have a string, you can do normal interpolation:
{ "f#{'o' * 2}".to_sym => 1, "bar" => 2 }
In your case, you could write
{ "#{custom_key}".to_sym => 1 }
However, all of this is completely unnecessary, since you can just write simply this, in any Ruby:
{ custom_key.to_sym => 1 }
Even better, since you're just turning everything into JSON immediately after, you don't even need symbolised keys; so these two expressions will have identical results:
{ custom_key.to_sym => 1 }.to_json
{ custom_key => 1 }.to_json
(Also note that what you state as examples of JSON -- both input and output -- are, in fact, not JSON, nor would .to_json output such. In JSON, as opposed to plain JavaScript object literal, keys must be double-quoted, and that is how to_json would produce it. Your input is a Ruby valid Ruby hash, though.)

You can just convert it to symbol and use hash_rocket syntax you will get the expected result
def jsonize(custom_key = "id")
{
my_json: {
custom_key.to_sym => 1,
name: "John"
}
}
end
#=> jsonize('foo')
#=> {
#=> :my_json => {
#=> :foo => 1,
#=> :name => "John"
#=> }
#=> }

Related

Sort items in a nested hash by their key

I have a nested hash with unsorted keys:
given = {
"lorem" => {
:AA => "foo",
:GR => "foo",
:BB => "foo"
},
"ipsum" => {
:ZZ => "foo",
:GR => "foo",
}
}
What I'm trying to accomplish is a hash with sorted keys:
goal = {
"ipsum" => {
:GR => "foo",
:ZZ => "foo"
},
"lorem" => {
:AA => "foo",
:BB => "foo",
:GR => "foo"
}
}
I have experimented with .each method and sort_by
given.each { |topic| topic[:key].sort_by { |k, v| k } }
But I'm getting an error message: TypeError: no implicit conversion of Symbol into Integer
Any help is greatly appreciated!
PS: I noticed with gem pry the output is already sorted. But in IRB it's not.
You can use group_by, and transform_values to transform the values inside each hash, also using sort_by plus to_h:
given.transform_values { |value| value.sort.to_h }.sort.to_h
# {"ipsum"=>{:GR=>"foo", :ZZ=>"foo"}, "lorem"=>{:AA=>"foo", :BB=>"foo", :GR=>"foo"}}
You're getting an error because when iterating over a hash, you have to local variables within the block scope to use, the key and its value, you're assigning only one (topic) and trying to get its key, which would be trying to access a key in:
["lorem", {:AA=>"foo", :GR=>"foo", :BB=>"foo"}]
Which isn't possible as is an array. You can update your code to:
given.each do |topic, value|
...
end
But anyway you'll need a way to store the changes or updated and sorted version of that topic values.
given_hash = {"lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}, "ipsum"=>{:ZZ=>"foo", :GR=>"foo"}}
Get keys
given_hash.keys
=> ["lorem", "ipsum"]
New sorted hash
new_hash = {}
given_hash.keys.sort.each do |sorted_key|
new_hash[sorted_key] = given[sorted_key]
end
=> {"ipsum"=>{:ZZ=>"foo", :GR=>"foo"}, "lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}}
There can be a better way to do this.

Ruby & fetching hash values magic

I'm trying to parse out JSON data and create my own dictionary to show a subset of the data. The thing is, I'm noticing that my input data changes based on what is scanned (with nmap). Some elements might be an array value, whereas some might not. The combinations seem to be pretty broad.
For instance, here is the simplest input where only an IP address was found:
{
'host' => {
'address' => {
'addr' => '192.168.0.1'
},
'status' => {...}
}
}
But then, the IP and MAC address might be found:
{
'host' => {
'address' => [{
'addrtype' => 'ipv4',
'addr' => '192.168.0.1',
},{
'addrtype' => 'mac',
'mac' => '00:AA:BB:CC:DD:EE',
},
'status' => {...}
}]
}
Those are just a couple examples. Other variations I've seen:
`host.class` = Array
`address.class` = Hash
`host['status'].class` = Array
etc...
As I go through to parse the output, I am first checking if the element is an Array, if it is, I access the key/values one way, whereas if it's not an array, I essentially have to duplicate my code with a few tweaks to it, which doesn't seem very eloquent:
hash = {}
if hosts.class == Array
hosts.each do |host|
ip = if host['address'].class == Array
host['address'][0]['addr']
else
host['address']['addr']
end
hash[ip] = {}
end
else
ip = if hosts['address'].class == Array
hosts['address'][0]['addr']
else
hosts['address']['addr']
end
hash[ip] = {}
end
puts hash
end
In the end, I'm just trying to find a better/eloquent way to produce a hash like below, while accounts for the possibility that an element may/may not be an Array:
{
'192.168.0.1' => {
'mac' => '00:aa:bb:cc:dd:ee',
'vendor' => 'Apple',
'ports' => {
'80' => {
'status' => 'open',
'service' => 'httpd'
}
'443' => {
'status' => 'filtered',
'service' => 'httpd'
}
}
},
192.168.0.2 => {
...
}
}
If there a ruby method that I haven't run across yet that will make this more fluid?
Not really... but you can make it always an array eg by doing something like:
hosts = [hosts] unless hosts.is_a?(Array)
or similar... then just pass that to your now-non-duplicated code. :)
The 20 lines of code in your question can be reduced to a single line using Array#wrap instead of conditionals, and using Enumerable#map instead of Enumerable#each:
Array.wrap(hosts).map { |host| [Array.wrap(host['address']).first['addr'], {}] }.to_h
Now that's magic!

Rails strong parameters for infinitely nestable model

I have a model, QueryElement, with a child class, QueryGroup.
QueryGroup can contain nested QueryElements.
Say QueryGroup has a propertyname, and QueryElement has a property filter (just for example)
So for strong parameters I have something like:
params.fetch(:query).permit(:name, :filter, :query_elements => [:name, :filter, :query_elements => [...]
And so on.
I could permit all (defeating the security of strong parameters, which I'd rather avoid), or manually step through the tree, which is much slower. That's my current approach.
Is there a better way?
Something like this:
REQUIRED = %i( name ).freeze
ALLOWED = (%i( filter query_elements ) + REQUIRED).freeze
MAX_DEPTH = 5
def ensure_params(hash, nest_level = 0) # ah I never come up with good names...
raise 'you went too deep man' if nest_level > MAX_DEPTH
hash.fetch_values(*REQUIRED)
hash[:query_elements] = ensure_params(hash[:query_elements], nest_level + 1) if hash[:query_elements]
hash.slice(*ALLOWED)
end
In IRB:
> ensure_params({ :filter => 2, :name => 'test', unpermitted_param: :something, :query_elements => { filter: 3, name: 'test', nested_unpermitted: 13 } })
# => {:filter=>2, :query_elements=>{:filter=>3, :name=>"test"}, :name=>"test"}
> ensure_params({ name: 1, query_elements: { notname: 1 } })
KeyError: key not found: :name
> MAX_DEPTH = 3
# => 3
> ensure_params({ name: 1, query_elements: { name: 1, query_elements: { name: 1, query_elements: { name: 1, query_elements: { name: 1, query_elements: { name: 1 } } } } }})
RuntimeError: you went too deep man
There is probably some improvements that could be done, like converting the keys to symbols, have a better error message to tell you at which nest level there is a missing key, etc.

Hash rocket vs colon in render json:

I am rendering a response to a POST request from a webhook. I just realized that when I render json: thingee and I log thingee it has hash rockets which are not valid json.
I've seen where people puts the hash and it looks fine, but that's not what I'm doing, I'm rendering a hash as JSON in response to a POST..
When rendered, my hash looks like this:
{"rates"=>[{"service_name"=>"Standard", "service_code"=>"f48",
"total_price"=>"390",},{"service_name"=>"Expedited", "service_code"=>"f34",
"total_price"=>"640"}]}
But I need it to be valid JSON and look like this:
{"rates":[{"service_name":"Standard", "service_code":"f48",
"total_price":"390",},{"service_name":"Expedited", "service_code":"f34",
"total_price":"640"}]}
Thanks
Don't worry so much. Its perfectly fine.
In Ruby the hashrocket syntax needs to be used whenever you want to have a hash key that is not a symbol:
irb(main):002:0> { foo: 1, 'baz': 2 }
=> {:foo=>1, :baz=>2} # coerces 'baz' into a symbol
irb(main):003:0> { foo: 1, 'baz' => 2 }
=> {:foo=>1, "baz"=>2}
However when you pass the hash render: { json: #foo } the hash is passed to JSON.generate which converts the ruby hash to valid JSON.
irb(main):006:0> JSON.generate({ "foo" => 1, "baz" => 2 })
=> "{\"foo\":1,\"baz\":2}"
irb(main):007:0> JSON.generate({ foo: 1, baz: 2 })
=> "{\"foo\":1,\"baz\":2}"
irb(main):008:0> { foo: 1, baz: 2 }.to_json
=> "{\"foo\":1,\"baz\":2}"

Why savon :attributes! not working with an object called Objects

When supplying savon with:
hash = {
"Objects" => { //stuff here },
:attributes! => { "Objects" => {"xsi:type" => "Something"}}
}
I get:
<Objects>...</Objects>
When supplying savon with anything else i get the expected result:
hash = {
"foo" => { //stuff here },
:attributes! => { "foo" => {"xsi:type" => "Something"}}
}
I get:
<foo xsi:type="Something"></foo>
I must use the string "Objects" as the key. I am coding to a 3rd party SOAP web service. I cannot use a symbol because the first letter would become a lower cap.
thanks,
You have to change :attributes! to :#xsi:type=>"Something" within the hash where you want the attribute
Like:
"foo"=>{:#xsi:type=>'something', //stuff here}

Resources