Accessing values of json structure in perl
Asked Answered
L

3

7

I have a json structure that I'm decoding that looks like this:

  person => {
    city => "Chicago",
    id => 123,
    name => "Joe Smith",
    pets => {
      cats => [
                { age => 6, name => "cat1", type => "siamese", weight => "10 kilos" },
                { age => 10, name => "cat2", type => "siamese", weight => "13 kilos" },
              ],
      dogs => [
                { age => 7, name => "dog1", type => "siamese", weight => "20 kilos" },
                { age => 5, name => "dog2", type => "siamese", weight => "15 kilos" },
              ],
    },
  },
}

I'm able to print the city, id, name by doing:

foreach my $listing ($decoded->{person})
{ 
    my $city = $listing->{city};
    my $name = $listing->{name};
    name - $city - \n";
}

however, I'm unsure of how to print the pets->cats or pets->dogs. I'm able to do a dump of them by:

my @pets = $listing->{pets}->{cats};
dump @pets;

but I'm not sure how to access them through the hash structure.

Ladanum answered 24/11, 2011 at 14:10 Comment(0)
F
9

Assuming your $listing is a person you have to dereference array and hash refs.

# as long as we are assuming $listing is a person
# this goes inside the foreach you posted in your
# question.

# this will print all cats' names
foreach my $cat ( @{ $listing->{pets}->{cats} } )
{
    # here $cat is a hash reference

    say $cat->{name}; # cat's name
}

and so on for other stuff.

To access them from the structure you can do:

say $listing->{pets}->{cats}->[0]->{name}; # this will print 'cat1'
Flamsteed answered 24/11, 2011 at 14:18 Comment(0)
A
10

Digging into a big structure is pretty simple, once you know the rules:

  • Wrap hash keys in {}
  • Wrap array indexes in []
  • If your top level variable is a reference, use -> before the first identifier.
  • After the first set of braces or brackets, additional arrows (->) are optional.

So: * $data->{person}{name} returns 'Joe Smith' * $data->{person}->{name} also returns 'Joe Smith' * $data->{pets}{cats}[0]{age} returns 6.

For way more detail on this topic, see the Perl Data Structures Cookbook (perldoc perldsc)

When you work with big structures like this there are some important things to be aware of. The biggest of these is autovivification. Autoviv means that Perl will automatically make data structure elements pop into existence for you to make your life easier. Unfortunately it can also make things difficult.

For example, autoviv is great when I do this:

my $data;
$data->{horse}[0]{color} = 'brown';

Autoviv magically turns $data into a hashref that contains the key horse with an array ref as its value. The array ref gets populated by a hash ref. The final hash ref then gets the key value pair of color => brown.

The problem comes in when you are walking a structure and do deep tests for existence:

# Code from above continues:

if( exists $data->{cat}[5]{color} ) {
    print "Cat 5 has a color\n";
}

use Data::Dumper;
print Dumper $data;

Here, autovivification burns you by creating a bunch of junk in data, here's the program output:

$VAR1 = {
      'cat' => [
                 undef,
                 undef,
                 undef,
                 undef,
                 undef,
                 {}
               ],
      'horse' => [
                   {
                     'color' => 'brown'
                   }
                 ]
    };

Now you can guard against this kind of thing by carefully testing each layer of your structure for existence, but it's a huge pain in the butt. Instead, I prefer to use Data::Diver.

use Data::Diver qw( Dive );

my $dog_20_color = Dive( $data, 'dog', 20, 'color' );
print "Dog 20 is $dog_20_color\n" if defined $dog_20_color;

$data is unchanged here.

Also, you may have noticed that since Dive takes a list of keys or indexes, that means its easy to programatically build up a list of keys/indexes and descend an arbitrary path in your code.

Data::Diver can be a real life saver when you have to do a lot of manipulation of big, wonky data structures.

Anthe answered 24/11, 2011 at 16:59 Comment(0)
F
9

Assuming your $listing is a person you have to dereference array and hash refs.

# as long as we are assuming $listing is a person
# this goes inside the foreach you posted in your
# question.

# this will print all cats' names
foreach my $cat ( @{ $listing->{pets}->{cats} } )
{
    # here $cat is a hash reference

    say $cat->{name}; # cat's name
}

and so on for other stuff.

To access them from the structure you can do:

say $listing->{pets}->{cats}->[0]->{name}; # this will print 'cat1'
Flamsteed answered 24/11, 2011 at 14:18 Comment(0)
B
2
my @pets = $listing->{pets}->{cats};

This isn't doing what you think it is. $listing->{pets}->{cats} contains a reference to an array. Your new @pets array ends up contains just one element - the array reference.

What you actually need is

my @pets = @{ $listing->{pets}{cats} };

This deferences the array reference and gets you the actual array. Notice that I've also dropped the optional second arrow in the expression.

Once you've got the array, each element of it is a hash reference.

foreach (@pets) {
  say $_->{name};
  # etc ...
}

Of course, you don't need the intermediate array at all.

foreach (@{ $listing->{pets}{cats} }) {
  say $_->{name};
}
Bivins answered 24/11, 2011 at 14:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.