Search code examples
perlperl-module

Parse JSON using Perl


I have written a test script to perform certain functions. Script works as expected. Currently,The arguments required to run the script are passed from the command line using Getopt::Long.I would like to move the command line arguments to a json file. The Endpoint ip will still be passed as command line arg. I want the endpoint ip to act as a key. For instance, if the endpoint is 1.1.1.1, I want to get the client_ip,client_interface_ip,originip,....,port that is listed under endpoint id 1.1.1.1 in the json config file mentioned below. How do i do that ?

current version of the script:

 use Getopt::Long;

     my ($self) = @_;

        GetOptions (
            "endpoint|e=s"           => \$self->{'endpoint'},
            "aggregator|a=s"         => \$self->{'aggregator'},
            "port|pt=s"              => \$self->{'port'},
            "client|c=s"             => \$self->{'client'},
            "client_interface|ci=s"  => \$self->{'client_interface'},
            "origin|o=s"             => \$self->{'origin'},
            "origin_interface|oi=s"  => \$self->{'origin_interface'},
            "interfacename|ot=s"     => \$self->{'i1'},
            "interfacename2|it=s"    => \$self->{'i2'},
             ) || $self->abort( "Invalid command line options.
                   Valid options are endpoint,aggregator,port,client,client_interface,
                     origin,origin_interface,outertunnel,innertunnel,");

       #Terminate the script execution if the reqd args are not passed
       my @required_args = qw(endpoint aggregator port client client_interface            origin origin_interface 
                              );

       for my $command_line_arguments (@required_args) {
         unless ($self->{$command_line_arguments}) {
          $self->abort('missing required argument ' . $command_line_arguments);
         }
       }

  $self->{'tObj'} =  QA::crypto::tunnels->new
      ('host'=> $self->{'endpoint'})
         or $self->abort('[Could not create a QA::Crypto::tunnels object.]');

json file for arguments:

{
        "Endpoints": [{

                "endpoint": "1.1.1.1",
                "client_ip": "3.4.5.6",
                "client_interface_ip": "10.11.12.14",
                "origin": "a.a.a.a",
                "origin_interface": "15.16.17.18",
                "interfacename": "name",
                "interfacename2": "name1",
                "sl": 19,
                "port": 362
        }, {

                "endpoint": "2.2.2.2",
                "client_ip": "19.20.21.22",
                "client_interface_ip": "23.24.25.26",
                "origin": "1.2.3.4",
                "origin_interface": "5.6.7.8",
                "interfacename": "interface name",
                "interfacename2": "interfacename_2",
                "sl": 19,
                "port": 366
        }]
}




#!/usr/bin/perl

use strict;
use warnings;
use JSON;

my $json;
{
   open my $fh, "<", "cfg.txt"
      or die("Can't open file \"cfg.json\": $!\n");
   local $/;
   $json = <$fh>;
}

my $data = decode_json($json);
$json = JSON->new->utf8->pretty->encode($data);

Solution

  • The first question is about a suitable design for the JSON file. Hashes can serve well here while the arrayref doesn't seem needed at all. The endpoint values can be the keys, with their values being hashrefs with key-related information. Keep the endpoint value in its hashref as well, if you wish.

    The key "Endpoints" in your JSON doesn't seem to do much as it stands. But if you do need it, perhaps since there will be other types of keys, then you could have for its value another hashref, which will contain the hashref with endpoint values as keys.

    For example

    { 
       "Endpoints": { 
            "1.1.1.1": { "client_ip": "3.4.5.6",     ... },
            "2.2.2.2": { "client_ip": "19.20.21.22", ... },
            ...
       }, 
       "OtherKeys": { ... }, ...
    }
    

    The last values should not end with commas. See JSON format.

    When you bring this into Perl you'll have nested hashrefs, like

    $data = { 
        Endpoints => { 
             1.1.1.1 => { client_ip => '3.4.5.6',     ... }, 
             2.2.2.2 => { client_ip => '19.20.21.22', ... },
        }, 
        OtherKeys => { ... }, 
    };
    

    Then the values are retrieved simply as

    my $client_ip = $data->{Endpoints}{'1.1.1.1'}{client_ip};
    

    For example, retreive all endpoints and list client_ip for them

    my @endpoints = keys %{$data->{Endpoints}};
    foreach my $ip (@endpoints) {
        say $data->{Endpoints}{$ip}{client_ip};
    }
    

    See Using References in perlref, and this post is entirely about it. Also see perlreftut and perldsc.

    We can inspect (see) the whole data structure with Data::Dumper

    use Data::Dumper;
    my $data = decode_json($json);
    print Dumper($data);
    

    There is a number of other packages for working with nested data structures.

    Note that the JSON package comes with encode_json and related methods, so you can create that file programmatically. This may help in getting its format right and with maintenance in the future.