Godzilla74
Godzilla74

Reputation: 2502

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?

Upvotes: 0

Views: 107

Answers (2)

moveson
moveson

Reputation: 5213

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!

Upvotes: 0

Taryn East
Taryn East

Reputation: 27747

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. :)

Upvotes: 1

Related Questions