skip to Main Content

I have created a script to get some information from various external sources, the results should then be in json format. There is a lot of data and I push everything to an array in a loop, then print the json array after everything has been completed, an extract of that loop part of the script:

#!/usr/bin/perl
use JSON -convert_blessed_universally;
use strict;
use warnings;
my @json_arr;
my @servers = ("SERVER1", "SERVER2");
my @details = ("SERVER1,10.1.2.3,Suse Linux",
               "SERVER2,10.1.2.4,Windows 10",
               "SERVER3,10.1.2.5,Windows XP");
my $json = JSON->new->convert_blessed;

foreach my $server(@servers) {
    foreach (@details) {
        my @detail = split(',',$_);
        if ($server eq $detail[0]) {
          push @json_arr, {name => "$server", ip => "$detail[1]", os => "$detail[2]"};
      }
   }
}
my $result = $json->encode(@json_arr);
print $result;

This gives an output of:

[
   {
      "name" : "SERVER1",
      "ip" : "10.1.2.3",
      "os" : "Suse Linux",
   },
   {
      "name" : "SERVER2",
      "ip" : "10.1.2.4",
      "os" : "Widows 10"
   }
]

and a screenshot:

enter image description here

I am however trying to do it by setting a key element instead and having the additional data as children of the device name, i.e:

{
  "instance" : [
    {
      "SERVER1" : {
        "ip" : "10.1.2.3",
        "os" : "Suse Linux"
      },
      "SERVER2" : {
         "ip" : "10.1.2.4",
         "os" : "Windows 10"
      }
    }
  ]
}

So I have tried a few things, including something like the below, then pushing to array, but I am getting funny results and just not getting the desired results.

my $json = '{
   "instance" : [
       $server => {
          ip => "$detail[0]",
          os => "$detail[1]"
       }
   ] 
}';
push @json_arr, $json;

2

Answers


  1. It only takes a small re-arrangement

    use warnings;
    use strict;
    use feature 'say';
    
    use Data::Dumper;
    use JSON::XS;
    
    ...
    
    my @json_ary;
    
    foreach my $server (@servers) {
        foreach (@details) {
            my @detail = split /,/; 
            if ($server eq $detail[0]) {
                #push @json_ary, {name => "$server", ip => "$detail[1]" ...
                push @json_ary, 
                    { $server => { ip => $detail[1], os => $detail[2] } } 
            }
        }   
    }
    
    print Dumper @json_ary;
    
    # Encode `{ instance => @json_ary }` for the desired output
    my $json = JSON->new->convert_blessed;
    my $result = $json->pretty->encode( { instance => @json_ary } );
    
    say $result;
    

    A few notes

    • No need for quotes around variables, since they get evaluated anyway ("$detail[0]" –> $detail[0] etc)

    • No need for quotes around hash keys, as a syntax convenience: key => 'value' is OK (and if the value is a variable it’s just key => $var). That is, as long as there are no spaces in the key name

    • One way to pretty-print an existing JSON string:

      print JSON::XS->new->ascii->pretty->encode(decode_json $json_str);
      

    There may be a question of whether a hashref around each server entry (JSON objects) is needed or not; the above was confirmed in a comment after a discussion so I settled with it.

    But if the output only needs a hashref with entries for servers in the array (and not an array of hashrefs for each server) then the code in the question can be modified to

    my %server_details;
    
    foreach my $server (@servers) {
        foreach (@details) {
            my @detail = split /,/; 
            if ($server eq $detail[0]) {
                $server_details{$server} = { ip => $detail[1], os => $detail[2] }
            }
        }   
    }
    

    Now this hash has details for all servers, and can be encoded with the key instance. Then it is not clear to me what the overall structure should be; one option is

    my $result = JSON->pretty->encode( { instance => [ %server_details ] }  );
    

    This would be suitable if there is more data in reality. If not then there may not be any need for an arrayref, but encode the usual

    { instance => %server_details }
    
    Login or Signup to reply.
  2. The problem is that you are adding hashes to an array (push @json_arr, ...) when you mean to add to a hash ($instance{ $server_name } = ...).

    my %servers = map { $_ => 1 } @servers;
    
    my %instance;
    for ( @details ) {
       my ( $name, $ip, $os ) = split /,/;
       next if !$servers{ $name };
    
       $instance{ $name } = {
          ip => $ip,
          os => $os,
       };
    }
    
    my @instances = %instance;
    
    my $data = { instance => @instances };
    

    Or using map:

    my %servers = map { $_ => 1 } @servers;
    
    my %instance = (
       map { $_->[0] => { ip => $_->[1], os => $_->[2] } }
          grep $servers{ $_->[0] },
             map [ split /,/ ],
                @details
    );
    
    my @instances = %instance;
    
    my $data = { instance => @instances };
    

    This produces the following, as requested:

    {
       "instance" : [
          {
             "SERVER1" : {
                "ip" : "10.1.2.3",
                "os" : "Suse Linux"
             },
             "SERVER2" : {
                "ip" : "10.1.2.4",
                "os" : "Windows 10"
             }
          }
       ]
    }
    

    (This is different than zdim’s first solution.)

    Note that I got rid of the nested loops because they are nasty. For a few items, it’s not a problem. But performance would suffer is @servers or @details would become large. So it’s a bad idea, and a bad habit to get into.


    Having an array which only even has a single element is weird. Did you perhaps want

    {
       "instance" : {
          "SERVER1" : {
             "ip" : "10.1.2.3",
             "os" : "Suse Linux"
          },
          "SERVER2" : {
             "ip" : "10.1.2.4",
             "os" : "Windows 10"
          }
       }
    }
    

    This would be achieved by replacing

    my $data = { instance => @instances };
    

    with

    my $data = { instance => %instance };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search