print JSON objects using perl - perl

I am new to perl and i had written below snippet of code to get the JSON objects from the data. But the input data has both hashes and arrays, So how do i get those values for "id" which is an array ?
use strict;
use warnings;
use Data::Dumper;
use JSON
my $data = '{"total":325,"id": [78,234,737,1253,1459,1733,2166,2653,2855,3133,3414,3538,3729,3905,3991,4110,4160,4536,4692,4701]}';
print Dumper($data);
my #hash_ref = from_json($data);
foreach my $hash_scalar (#hash_ref) {
foreach (keys %{$hash_scalar}) {
print "$_ => ${$hash_scalar}{$_}\n";
}
}
Output Getting
#
$VAR1 = '{"total":325,"id":
[78,234,737,1253,1459,1733,2166,2653,2855,3133,3414,3538,3729,3905,3991,4110,4160,4536,4692,4701]}';
id => ARRAY(0x2afee4c)
total => 325

The id is a key in the hash reference you get back, and you want to grab its value, which is an array reference. You can either keep that as a reference or grab its elements right away:
#!perl
use v5.24;
use JSON qw(from_json);
my $data = '{"total":325,"id": [78,234,737]}';
my $json = from_json($data);
# get the hash key to get the hash value, which is an array reference
my $ids_array_ref = $json->{id};
# OR, use the postfix dereference to get the ids as a normal list
my #ids_array = $json->{id}->#*;
# OR, use the older circumfix notation (before Perl v5.24). These are all
# the same:
my #ids_array = #{ $json->{id} };
my #ids_array = #{ $ids_array_ref };
my #ids_array = #$ids_array_ref;
For example:
#!perl
use v5.24;
use JSON qw(from_json);
my $data = '{"total":325,"id": [78,234,737]}';
my $json = from_json($data);
foreach my $id ( $json->{id}->#* ) {
say "Got id $id";
}
This outputs:
Got id 78
Got id 234
Got id 737
But, you have to handle the other hash value types too. To decide how to print something, you have to check if it's a reference and what sort of reference it is (I ignore the other sorts here):
#!perl
use v5.24;
use JSON qw(from_json);
my $data = '{"total":325,"id": [78,234,737]}';
my $json = from_json($data);
foreach my $key ( keys $json->%* ) {
print "$key: ";
if( ref $json->{$key} eq 'ARRAY' ) {
say join ' ', $json->{$key}->#*;
}
elsif( ref $json->{$key} ) { # all other reference types
warn "I don't handle this yet"
}
else {
say $json->{$key};
}
}
But, you might have deeper levels of nesting, so you'd need to think about that too if you want to output everything. If I know what keys I want, I don't try to go through everything in a general way. I go directly to want I want:
#!perl
use v5.24;
use JSON qw(from_json);
my $data = '{"total":325,"id": [78,234,737]}';
my $json = from_json($data);
say "id: ", join ' ', $json->{id}->#*;
say "total: ", $json->{total};
Your original code was doubly complicated. The result of from_json is the reference that represents the JSON data structure. That's a JSON object, which is what Perl calls a hash, so you get a hash reference back.
my $hash_ref = from_json( $data );
Your example almost worked because you got an array of one element, which is that hash reference. The other foreach then iterates over that one element. Get rid of that outer array and you get rid of the outer loop.
We cover references and data structures in Intermediate Perl, although the perldsc is good too. We have a long examples of deeply-nested and recursive data structures.
If you're new, you may want to start with Learning Perl, though.

My Perl is getting rusty, but I think something like this should do the trick. You need to de-reference your array reference with another #{...} like #{${$hash_scalar}{$_}}.
use strict;
use warnings;
use Data::Dumper;
use JSON
my $data = '{"total":325,"id": [78,234,737,1253,1459,1733,2166,2653,2855,3133,3414,3538,3729,3905,3991,4110,4160,4536,4692,4701]}';
print Dumper($data);
my #hash_ref = from_json($data);
for my $hash_scalar (#hash_ref) {
for my $key (keys(%{$hash_scalar})) {
print("$key => #{${$hash_scalar}{$_}}\n");
}
}

Perhaps following code will cover your question in more complete extent.
NOTE: I've added a hash in data block as bonus
use strict;
use warnings;
use feature 'say';
use JSON;
use Data::Dumper;
my $debug = 0;
my $data = '{ "total":325,
"id": [78,234,737,1253,1459,1733,2166,2653,2855,3133,3414,3538,3729,3905,3991,4110,4160,4536,4692,4701],
"person": {
"first": "John",
"last": "Smith",
"age": 27,
"occupation": "accountant",
"wife": "Maria Smith",
"son": "Alex Smith",
"daughter": "Samantha Smith",
"dog": "Sparky",
"hobby": "photography"
}
}';
say Dumper($data) if $debug;
my $json = from_json($data);
say Dumper($json) if $debug;
while( my($k,$v) = each %{$json} ) {
say "Key: $k";
if( ref $v eq 'ARRAY' ) {
say "\tValue is ARRAY";
for ( #{$v} ) { say "\t\t[$_]" }
} elsif( ref $v eq 'HASH' ) {
say "\tValue is HASH";
while( my($hk,$hv) = each %{$v} ) {
say "\t\t$hk => $hv";
}
} else {
say "\tValue: $v";
}
}
say '-' x 40;
say 'Element with index 5 is ' . $json->{id}[5];
say 'Name of the son is ' . $json->{person}{son};
say 'Name of the daughter is ' . $json->{person}{daughter};
Output
Key: person
Value is HASH
son => Alex Smith
occupation => accountant
daughter => Samantha Smith
age => 27
wife => Maria Smith
dog => Sparky
last => Smith
first => John
hobby => photography
Key: id
Value is ARRAY
[78]
[234]
[737]
[1253]
[1459]
[1733]
[2166]
[2653]
[2855]
[3133]
[3414]
[3538]
[3729]
[3905]
[3991]
[4110]
[4160]
[4536]
[4692]
[4701]
Key: total
Value: 325
----------------------------------------
Element with index 5 is 1733
Name of the son is Alex Smith
Name of the daughter is Samantha Smith

Related

Perl print specific value from decoded json

I'm trying to decode a json then only print specific values from it. I want to get the front and back from the json and save the file. Code is not getting past the decode_base64 and says "Not a HASH reference"
use strict;
use warnings;
use JSON;
use Data::Dumper
use MIME:Base64
my $imageDir = "/usr/documents/images/";
my $testJson = '[{ "front":"", "back":"", "shortID":"", "longID":"" } ]';
my $Json = decode_json($testJson);
print Dumper $Json;
my $fImage = decode_base64($Json->{front});
my $fImagefile = saveFile('Front.jpg',$fImage);
my $bImage = decode_base64($Json->{back});
my $bImagefile = saveFile('Back.jpg',$bImage);
return ($fImagefile, $bImagefile);
sub saveFile{
my ($file $image) = #_;
my imageFile = $imageDir.$file;
open (IMGFILE, '>'.$imageFile) or die;
print IMGFILE $image;
close IMGFILE;
return $imageFile;
}
#ikegami already said this in a comment but I'll expand on it.
You have a JSON fragment that's an array, and the element in the array is a hash. T. You have one more level to drill down into to get what you want. The perldsc (Perl Data Structures Cookbook) has lots of hints and tricks for multi-level data structures:
my $testJson = '[{ "front":"", "back":"", "shortID":"", "longID":"" } ]';
my $Json = decode_json($testJson);
print Dumper $Json;
In your Dumper output you should see something like this. It's an anonymous array with a hashref as its first and only element:
$VAR1 = [
{
'longID' => '',
'shortID' => '',
'front' => '',
'back' => ''
}
];
If you want to process only that element, you can get the first element of the array reference. This is going to be the hash reference that you want to process:
my $element = $Json->[0];
my $fImage = decode_base64($element->{front});
...
But, you might want to process all the elements. Simply wrap a foreach around it:
foreach my $element ( #$Json ) {
my $fImage = decode_base64($element->{front});
...
}
Even though that array dereference is simple, the postfix dereferencing introduced in v5.26 is quite nice and what I use most often:
foreach my $element ( $Json->#* ) {
my $fImage = decode_base64($element->{front});
...
}

Accessing and modifying a nested hash based on a dot separated string

I have a string as input, say apple.mango.orange = 100
I also have a hash reference:
$inst = {
'banana' => 2,
'guava' => 3,
'apple' => {
'mango' => {
'orange' => 80
}
}
};
I want to modify the value of orange using the input string. Can someone please help me how I could do this?
I tried splitting the string into (key, value) pair. I then did the following on the key string:
my $key2 = "\$inst->{".$key."}";
$key2 =~ s/\./}->{/g;
$$key2 = $value;
This does not work as intended. Can someone help me out here? I have read the Perl FAQ about not using a variable value as variable but I am unable to think of an alternative.
You are building string that consists of (buggy) Perl code, but you never ask Perl to execute it. ...but that's not the right approach.
sub dive_val :lvalue {
my $p = \shift;
$p = \($$p->{$_}) for #_;
$$p
}
my #key = split /\./, "apple.mango.orange";
dive_val($inst, #key) = $value;
or
use Data::Diver qw( DiveVal );
my #key = split /\./, "apple.mango.orange";
DiveVal($inst, map \$_, #key) = $value;
Not only is a symbolic reference a very bad idea here, it doesn't even solve your problem. You're building an expression in $key2, and just jamming another dollar sign in front of its name won't make perl execute that code. For that you would need eval, which is another bad idea
You can install and use the Data::Diver module, which does exactly this sort of thing, or you can simply loop over the list of hash keys, picking up a new hash reference each time and assigning the value to the element with the last key
The biggest issue is actually parsing the incoming string into a list of keys and a value. This code implements a subroutine apply which applies the implied operation in the string to a nested hash. Unless you are confident of your data, it needs some error checking addingto make sure each of the keys in the list exists. The Data:;Dumper output is just to demonstrate the validity of the result
use strict;
use warnings 'all';
use Data::Dumper;
my $inst = { 'banana' => 2, 'guava' => 3, 'apple' => { 'mango' => { 'orange' => 80 } } };
my $s = 'apple.mango.orange = 100';
apply($s, $inst);
print Dumper $inst;
sub apply {
my ($operation, $data) = #_;
my ($keys, $val) = $operation =~ /([\w.]+)\s*=\s*(\d+)/;
my #keys = split /\./, $keys;
my $last = pop #keys;
my $hash = $data;
$hash = $hash->{$_} for #keys;
$hash->{$last} = $val;
}
output
$VAR1 = {
'banana' => 2,
'apple' => {
'mango' => {
'orange' => '100'
}
},
'guava' => 3
};

Not able to print value of this complex datastructure in perl

Question explained as comments in the code:
I have a following piece of code in which i am trying to make a hash in which the key itself is a reference to some other array.
my #arr1=(1,2,3,4,5,6,7,8,9,10);
my #arr2=(1001,1002,1003);
$FILES_1=\#arr1;
$num1=2;
$FILES_2=\#arr2;
$num2=4;
#FILES=($FILES_1, $FILES_2);
#NUMS=($num1,$num2);
fun (\#FILES,\#NUMS);
sub fun{
my ($rFILES,$rNUMS) = #_;
print "\n --${$rFILES}[0]->[2] \n "; # This is same as below
print "\n --$rFILES->[0]->[2] \n "; # This is same as above
my %hash=();
$hash{$rFILES->[0]} = $rNUMS->[0];
my $test = $rFILES->[0];
print "\nTEST : $test->[1]";
my #key = keys %hash;
print "\nKey 1 = $key[0]"; # This prints scalar value as below
print "\ntest = $test "; # This prints scalar value as above
print "\nKey 1->[1] = ${$key[0]}->[1]"; #PROBLEM : THIS DOESNT PRINT SAME AS BELOW
print "\ntest->[1] = $test->[1] "; #THIS PRINTS AS EXPECTED.
}
Output:
--3
--3
TEST : 2
Key 1 = ARRAY(0x1bbb540)
test = ARRAY(0x1bbb540)
Key 1->[1] =
test->[1] = 2
Are we not supposed to keep a key of a hash as reference to some array? Why is the value "2" not printed?
A hash key is always a string, you can't store a reference in there. I can't think of a reason you'd want this either.
You should really always use strict and use warnings. If you added those and declared your variables with my properly, you'd have gotten a warning:
Can't use string ("ARRAY(0x85e628)") as a SCALAR ref while "strict refs" in use
The syntax you're using:
${$key[0]}
says $key[0] is a reference to a scalar. It isn't, it's a string with the address of what the reference used to be, as you can't use a reference as a hash key they become strings.
Update:
You probably want something like this instead:
my #filegroups = (
{ number => 1, files => ['file1','file2'] },
{ number => 2, files => ['file3'] },
);
Accessed as:
foreach my $filegroup ( #$filegroups ) {
print "my number is $filegroup->{number}\n";
print " and I have ".scalar( #{ $filegroup->{ files } } )." files\n";
}
If you need extra speed to access the structure by group number (don't bother unless you have hundreds of groups or thousands and thousands of accesses), index them:
my %filegroupindexes = map { $_->{ number } => $_ } values #$filegroups;
Then you can get to the group like this:
print "there are ".scalar( #{ $filegroupindexes{ 1 }->{ files } } )." files in group 1\n";
As a last hint, for printing complex data structures Data::Printer is my favourite:
use Data::Printer colored => 1;
p( #filegroups );
You need Tie::RefHash if you want to use references as hash keys.

Store and get values from Perl hash tables

I want to store names in hash or array, which are in format
(e.g apple<->banana , orange<->papaya).
And now I have half information like apple or papaya which I need to look in that hash table and get the full combination apple<->banana and store it in a variable... :)
Hope my question is clear actually i read few hash documents and every where it's mentioned to search with full name ... so I need to search with half name or 1st word.
Assuming your input file is like:
apple<->banana
orange<->papaya
Here is a way to do the job:
#!/usr/bin/perl
use strict;
use warnings;
my %corresp;
while(<DATA>) {
chomp;
my ($k, $v) = split/<->/,$_;
$corresp{$k} = $v;
}
my %reverse = reverse %corresp;
my $search = 'apple';
if (exists$corresp{$search}) {
say "$search = $corresp{$search}";
} elsif(exists$reverse{$search}) {
say "$search = $reverse{$search}";
} else {
say 'No match!';
}
__DATA__
apple<->banana
orange<->papaya
Try this one:
my %hash = (
'apple' => 'banana',
'orange' => 'papaya'
);
## the word is looking for
my $word = 'orange';
## checking using Key
if(defined($hash{$word})){
print "$word <=> $hash{$word}";
}
## checking using value
else{
## handling only one value, not all
my ($key) = grep { $hash{$_} eq $word } keys %hash;
print "$key <=> $hash{$key}" if $key;
}

How do I add a new Key,Value pair to a Hash in an array of hash in perl?

Hi I have a need to add a new key,value pair to the hash entries within an array of hashes.
Below is some sample code which does not work(simplified with only 1 array entry) The output of the print statement just contains the 1 entry.
my #AoH;
push #AoH, { TEST1 => 'testvalue' };
for my $hash (#AoH)
{
$hash{'TEST2'} = 'testvalue2';
print Dumper($hash);
}
What am I doing wrong?
Thank you.
This code looks a little strange so I am going to assume it was done like that for the purposes of showing it briefly here, but the main thing you need to do to fix your code is change:
$hash{'TEST2'} = 'testvalue2';
to:
$$hash{'TEST2'} = 'testvalue2';
or:
$hash->{'TEST2'} = 'testvalue2';
The extra '$' or '->' dereferences the hash reference '$hash'. Since neither is there, it treats $hash{'TEST2'} as a different variable: '%hash' (not '$hash') and assigns 'testvalue2' to that. You would have gotten a good error message:
Global symbol "%hash" requires explicit package name at - line XX
if you tried to run this code with:
use strict;
use warnings;
at the beginning... which you should always do, so do that every time from now on.
use strict;
use warnings;
use Data::Dumper;
my #AoH=();
my %data_source_hash=(
TEST1 => 'testvalue1',
TEST2 => 'testvalue2'
);
# adds whole hash as the array element
push #AoH,{ %data_source_hash };
print Dumper(#AoH);
#AoH=();
print "---------------------------\n";
# adds each hash $key, $value pair as an element
while ( my ($key, $value) = each %data_source_hash )
{
push #AoH, { $key => $value };
}
print Dumper(#AoH);
#AoH=();
print "---------------------------\n";
# adds extra hash entry to each array element
push #AoH, { TEST1 => 'testvalue' };
push #AoH, { TEST3 => 'testvalue3' };
foreach my $el (#AoH)
{
my $key = 'TEST2';
$$el{$key} = $data_source_hash{$key};
}
print Dumper(#AoH);