andy503 has asked for the wisdom of the Perl Monks concerning the following question:

hi venerable monks,

I'm new to Perl so please be fairly gentle...

I have the following code that's building a rather nice array of data, and I can get to that data. However I have a strong feeling that this is by luck rather than design, especially in the array / hash area near line 22/23!

#!/usr/bin/perl use strict; use warnings; use Data::Dumper; use LWP::Simple; use JSON; ### using api from http://mymovieapi.com/ # define url for get request # ghostbusters url my $url = "http://mymovieapi.com/?id=tt0087332&type=json&plot=simple&e +pisode=1&lang=en-US&aka=simple&release=simple&business=0&tech=0"; # receive JSON into $response, via get call using LWP::Simple my $response = get $url; # die if we have problems making the GET request die 'Error making GET request -> $url' unless defined $response; # usual print line for troubleshooting #print Dumper($response); ### using JSON's decode_json we decode the JSON string into an array @ +data # my array @data gets the decoded JSON string my @data = decode_json($response); #print Dumper($data); # for each element in the now decoded array, make them accessible foreach (@data) { print $_->{title} . " was released in year -> " . $_->{year} . " , + and it is rated " . $_->{rating}; }

first question... @data is an array but should I be rather trying to build a hash in order to more simply access the key pairs and deal with editing later on?

second question... with @data, I'm accessing the values via $_->{title}, which is a global modifier whatchamacallit, there must be a safer/better way to do this? something like foreach $element in @data... $element->{title} ?

third question... @data is not returning multiple results to necessitate foreach, but I can't access the results without it... I would like to follow my @data...; with something like $_->{title} but it bleats about an uninitialized value?

the JSON response is successful and is noted here so that you can see the depth of the array and the various key pairs.

$VAR1 = { 'genres' => [ 'Comedy', 'Fantasy', 'Sci-Fi' ], 'plot_simple' => 'Three unemployed parapsychology professors + set up sh op as a unique ghost removal service.', 'poster' => { 'imdb' => 'http://ia.media-imdb.com/images/M/M +V5BNTUzMDU 3OTEyNV5BMl5BanBnXkFtZTYwOTk1MDE5._V1_SY317_CR2,0,214,317_.jpg', 'cover' => 'http://imdb-poster.b0.upaiyun.com/ +000/087/33 2.jpg!cover?_upt=a7275e191389164607' }, 'title' => 'Ghost Busters', 'year' => 1984, 'imdb_url' => 'http://www.imdb.com/title/tt0087332/', 'country' => [ 'USA' ], 'also_known_as' => [ 'Ghostbusters' ], 'writers' => [ 'Dan Aykroyd', 'Harold Ramis' ], 'language' => [ 'English' ], 'rating_count' => 167966, 'type' => 'M', 'directors' => [ 'Ivan Reitman' ], 'actors' => [ 'Bill Murray', 'Dan Aykroyd', 'Sigourney Weaver', 'Harold Ramis', 'Rick Moranis', 'Annie Potts', 'William Atherton', 'Ernie Hudson', 'David Margulies', 'Steven Tash', 'Jennifer Runyon', 'Slavitza Jovan', 'Michael Ensign', 'Alice Drummond', 'Jordan Charney' ], 'imdb_id' => 'tt0087332', 'runtime' => [ '105 min' ], 'filming_locations' => 'Greystone Park & Mansion - 905 L +oma Vista Drive, Beverly Hills, California, USA', 'rating' => '7.8', 'release_date' => 19841202 };

Replies are listed 'Best First'.
Re: Array / Hash Confusion!
by tobyink (Canon) on Jan 07, 2014 at 19:40 UTC

    my @data = decode_json($response); is a big red flag. JSON's decode_json function doesn't return a list. It always returns a single scalar. This scalar will be a reference to either an array or a hash.

    Assigning a scalar value to the array variable @data is not wrong per se, but all it does is set the array to be a single-item array (hence the appearance of foreach "not working").

    In this case, it appears to be returning a reference to a hash.

    Try something like this:

    my $data = decode_json($response); printf( "'%s' was released in year %d and it is rated %s.\n", $data->{title}, $data->{year}, $data->{rating}, );
    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

      thanks for your reply. okay I see that it returns a single scalar and I understand that the result I'm getting is a hash.

      I don't understand the idea that the scalar is a reference to an array or a hash, which is clearly where I'm going wrong.

        perlreftut and perlref are the best places to start learning about references. A reference can be thought of as a pointer to an array or hash (and a few other datatypes too, but we'll stick with arrays and hashes for now). You can have multiple references (pointers) to the same hash or array, allowing them all to see the same data. Here's a quick example:

        use strict; use warnings; use feature 'say'; my @arr = (); my $ref1 = \@arr; my $ref2 = $ref1; push @arr, "Hello"; # To access an array or hash via the reference # we need to use a pointy arrow... say $ref1->[0]; # says "Hello" say $ref2->[0]; # says "Hello" again # We use an @{...} to "dereference" an array ref. # That is, to treat it like an array. push @{$ref1}, "World"; say $arr[1]; # says "World" say $ref2->[1]; # says "World" again # The dereferencing syntax can be interpolated into # strings. say "@{$ref2}"; # says "Hello World" my %h = (); # You can't store an array in a hash because hash # values are always scalars. $h{foo} = @arr; # an array in scalar context # evaluates to its length. # This stores "2" in the hash. # But you can store a reference, because a reference # is a hash. $h{bar} = $ref1; # Let's print that "Hello" string again, via the hash. say $h{bar}->[0]; # says "Hello" # Now let's take a reference to that hash. my $ref3 = \%h; # We can also print "Hello" via the hash reference say $ref3->{bar}->[0]; # says "Hello" # When you've got a big chain of pointy arrows to # navigate through references, you only need the # first one. say $ref3->{bar}[0]; # says "Hello" # Here's something a little advanced. We'll # assign a reference to a localized glob. our %h2; local *h2 = $ref3; # Now %h2 (which is a real hash, not a reference to # a hash) is an ALIAS for %h. say $h2{foo}; # says "2" delete $h2{foo}; # also deletes $h{foo}.
        use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
        You can think of a reference to an array (or to a hash) as a simple scalar variable that contains the memory address of that data structure. Something quite similar to a pointer if you ever used a language using pointers.

        could this perlref be of any help?

        If you tell me, I'll forget.
        If you show me, I'll remember.
        if you involve me, I'll understand.
        --- Author unknown to me
Re: Array / Hash Confusion!
by toolic (Bishop) on Jan 07, 2014 at 19:41 UTC
    Store the result of the JSON decode into a scalar:
    my $data = decode_json($response); print "$data->{title} was released in year -> $data->{year}, and it is + rated $data->{rating}\n";

      thanks for your reply. I understand I need to store the result in a scalar and thanks for your suggested code, which I've seen works in my env.

      it clearly works for "$data->{title}"; as this is a single result, but when "$data->{genres}"; this returns ARRAY(gobblydegook) as expected.

      how do I effectively test if a result is an array or not, and then jump into it? or should I be converting this into a hash and trying to look at key/value combinations?

        ref is handy:
        foreach my $key (keys %$data) { my $thing = $data->{$key}; if (ref $thing eq 'HASH') { useThingAsHashRef($thing); }elsif (ref $thing eq 'ARRAY') { useThingAsArray($thing); } }
        If you know it is an array:
        print "$_\n" for @{ $data->{genres} };

        See also:

Re: Array / Hash Confusion!
by Laurent_R (Canon) on Jan 07, 2014 at 19:46 UTC

    The decode_json function returns a reference to an array or a hash, not an array directly. So you really want to use a scalar variable.

    Update: Oops, two responses already said it before me while I was loosing time checking that I was not going to say something wrong or stupid. ;-)

Re: Array / Hash Confusion!
by locked_user sundialsvc4 (Abbot) on Jan 07, 2014 at 21:59 UTC

    Here is, I think, a “big light-bulb realization” about Perl ... at least it was for me.

    Variables are most-commonly declared as scalars, which are then used to refer to hashes or arrays.   (Called “hashrefs” and “arrayrefs,” respectively.)   It is not, in my experience, very common to find explicit @array or %hash variables.

    Why not?   I think it’s for two combined reasons:   versatility, and consistency.   Especially when more-elaborate data structures are being created ... arrays “of” hashes, hashes “of” arrays “of” hashes, and lots of even-stranger things, all of which are created using references.   (Both an array and a hash can contain “only a single scalar value” in each bucket that they can hold, but since “a reference” is “a single scalar value,” each bucket can “contain” anything-at-all, by reference.   So, you just quickly get into the habit of using references for everything, and Perl’s syntax is designed to make this very easy and expressive.

    One thing that can also be done is ... to have more than one hash or array (say ...) that refers to the same piece of data.   For example, you could have an array(ref) which contains references to “something,” and, simultaneously, a hash(ref) which contains references to exactly the same “somethings.”   It lets you create the rough-equivalent of an “index” to that data ... another, faster-for-you way to get to the same piece of data, in two ways.   Perl excels at in-memory data manipulation, and its internal implementations are very fast and robust.

      It is not, in my experience, very common to find explicit @array or %hash variables.

      Your experience is very different from mine. That'd be like declaring everything as void * in C.