Accessing nested JSON elements in Perl - json

I get an error when attempting to access the contents of my JSON array.
Here is the contents of my JSON array assets.json:
[{"id":1002,"interfaces":[{"ip_addresses":[{"value":"172.16.77.239"}]}]},{"id":1003,"interfaces":[{"ip_addresses":[{"value":"192.168.0.2"}]}]}]
Here is my code
#!/usr/bin/perl
use strict;
use warnings;
use JSON::XS;
use File::Slurp;
my $json_source = "assets.json";
my $json = read_file( $json_source ) ;
my $json_array = decode_json $json;
foreach my $item( #$json_array ) {
print $item->{id};
print "\n";
print $item->{interfaces}->{ip_addresses}->{value};
print "\n\n";
}
I get the expected output for $item->{id} but when accessing the nested element
I get the error "Not a HASH reference"

Data::Dumper is your friend here:
Trying this:
#!/usr/bin/env perl
use strict;
use warnings;
use JSON::XS;
use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Terse = 1;
my $json_array = decode_json ( do { local $/; <DATA> } );
print Dumper $json_array;
__DATA__
[{"id":1002,"interfaces":[{"ip_addresses":[{"value":"172.16.77.239"}]}]},{"id":1003,"interfaces":[{"ip_addresses":[{"value":"192.168.0.2"}]}]}]
Gives:
[
{
'interfaces' => [
{
'ip_addresses' => [
{
'value' => '172.16.77.239'
}
]
}
],
'id' => 1002
},
{
'interfaces' => [
{
'ip_addresses' => [
{
'value' => '192.168.0.2'
}
]
}
],
'id' => 1003
}
]
Important point of note - you have nested arrays (the [] denotes array, the {} a hash).
So you can extract your thing with:
print $item->{interfaces}->[0]->{ip_addresses}->[0]->{value};
Or as friedo notes:
Note that you may omit the -> operator after the first one, so $item->{interfaces}[0]{ip_addresses}[0]{value} will also work.

Related

Perl: hash from import JSON data, Dumper Outputs right data, However I can not access it

I have the following data in .json; actual values substituted.
{ "Mercury": [
{
"Long": "0.xxxxxx",
"LongP": "0.xxxxx",
"Eccent": "0.xxxx",
"Semi": "0.xxxx",
"Inclin": "0.xxxx",
"ascnode": "0.xx.xxxx",
"adia": "0.xxx",
"visual": "-0.xx"
}
]
}
This works fine:
my %data = ();
my $json = JSON->new();
my $data = $json->decode($json_text);
my $planet = "Mercury";
print Dumper $data; # prints:
This is all fine:
$VAR1 = {
'Mercury' => [
{
'Inclin' => '7.',
'Semi' => '0.8',
'adia' => '6.7',
'LongP' => '77.29',
'visual' => '-0.00',
'Long' => '60.000',
'Eccent' => '0.0000',
'ascnode' => '48.0000'
}
]
};
However when I try to access the hash:
my $var = $data{$planet}{Long};
I get empty values, why?
Problem 1
$data{$planet} accesses hash %data, but you populated scalar $data.
You want $data->{$planet} instead of $data{$planet}.
Always use use strict; use warnings;. It would have caught this error.
Problem 2
$data->{$planet} returns a reference to an array.
You want $data->{$planet}[0]{Long} (first element) or $data->{$planet}[-1]{Long} (last element) instead of $data->{$planet}{Long}. Maybe. An array suggests the number of elements isn't always going to be one, so you might want a loop.

Perl - from JSON to object/ hash

I have to code below:
#!/usr/intel/bin/perl
use strict;
use warnings;
use JSON::XS;
my $json = '{"Object1":{"Year":"2012","Quarter":"Q3","DataType":"Other 3","Environment":"STEVE","Amount":125},"Object2":{"Year":"2012","Quarter":"Q4","DataType":"Other 2","Environment":"MIKE","Amount":500}}';
my $arrayref = decode_json $json;
for my $array(#$arrayref){
for my $key (keys(%$array)){
my $val = $array->{$key};
print "$key: $val\n";
}
}
When I compile it, it print me the error "Not an ARRAY reference at generator.pl line 12.".
I want to parse the JSON to an object and get data according to the object with the attributes. How can I do it?
I expect after I parse it, I can use to compare string, print, loop it and so on.
It is not array reference, it is hash reference:
#!/usr/intel/bin/perl
use strict;
use warnings;
use JSON::XS;
use Data::Dumper;
my $json = '{"Object1":{"Year":"2012","Quarter":"Q3","DataType":"Other 3","Environment":"STEVE","Amount":125},"Object2":{"Year":"2012","Quarter":"Q4","DataType":"Other 2","Environment":"MIKE","Amount":500}}';
my $arrayref = decode_json $json;
print Data::Dumper->Dump([$arrayref], [qw(arrayref)]);
And output:
$arrayref = {
'Object2' => {
'Quarter' => 'Q4',
'Year' => '2012',
'Amount' => 500,
'DataType' => 'Other 2',
'Environment' => 'MIKE'
},
'Object1' => {
'Amount' => 125,
'DataType' => 'Other 3',
'Year' => '2012',
'Environment' => 'STEVE',
'Quarter' => 'Q3'
}
};
There are no arrays there; it is a hash of hashes.
my $hashref = decode_json $json;
for my $object_name (sort keys %$hashref){
print "In $object_name:\n";
for my $key (sort keys %{ $hashref->{$object_name} }){
my $val = $hashref->{$object_name}{$key};
print "$key: $val\n";
}
}

DBM::Deep is failing to import hashref having 'true' or 'false' values

I have the JSON text as given below :
test.json
{
"a" : false
}
I want to create the DBM::Deep hash for above JSON. My code is looks like as given below :
dbm.pl
use strict;
use warnings;
use DBM::Deep;
use JSON;
use Data::Dumper;
# create the dbm::deep object
my $db = DBM::Deep->new(
file => 'test.db',
type => DBM::Deep->TYPE_HASH
);
my $json_text = do {
open( my $json_fh, $path )
or die("Can't open \$path\": $!\n");
local $/;
<$json_fh>;
};
my $json = JSON->new;
my $data = $json->decode($json_text);
print Dumper($data);
# create dbm::deep hash
eval { $db->{$path} = $data; };
if ($#) {
print "error : $#\n";
}
I am getting below output/error on execution of above code:
Error
$VAR1 = {
'a' => bless( do{(my $o = 0)}, 'JSON::XS::Boolean' )
};
error : DBM::Deep: Storage of references of type 'SCALAR' is not supported. at dbm.pl line 26
It seems like, JSON internally uses JSON::XS which convert the 'true' value in JSON::XS::Boolean object and DBM::Deep is not able to handle this, while it can handle the null value.
While the above code is working fine for below inputs:
{
"a" : 'true' # if true is in quotes
}
or
{
"a" : null
}
I tried many thing, but nothing worked. Does anyone has any workaround?
The JSON parser you are using, among others, returns an object that works as a boolean when it encounters true or false in the JSON. This allows the data to be re-encoded into JSON without change, but it can cause this kind of issue.
null doesn't have this problem because Perl has a native value (undef) that can be used to represent it unambiguously.
The following convert these objects into simple values.
sub convert_json_bools {
local *_convert_json_bools = sub {
my $ref_type = ref($_[0])
or return;
if ($ref_type eq 'HASH') {
_convert_json_bools($_) for values(%{ $_[0] });
}
elsif ($ref_type eq 'ARRAY') {
_convert_json_bools($_) for #{ $_[0] };
}
elsif ($ref_type =~ /::Boolean\z/) {
$_[0] = $_[0] ? 1 : 0;
}
else {
warn("Unsupported type $ref_type\n");
}
};
&_convert_json_bools;
}
convert_json_bools($data);
Your code works fine for me, with the only change being to set
my $path = 'test.json';
You should check your module version numbers. These are the ones that I have
print $DBM::Deep::VERSION, "\n"; # 2.0013
print $JSON::VERSION, "\n"; # 2.90
print $JSON::XS::VERSION, "\n"; # 3.02
and I am running Perl v5.24.0
The dumped output is as follows
Newly-created DBM::Deep database
$VAR1 = bless( {}, 'DBM::Deep::Hash' );
output of $json->decode
$VAR1 = {
'a' => undef
};
Populated DBM::Deep database after the eval
$VAR1 = bless( {
'test.json' => bless( {
'a' => undef
}, 'DBM::Deep::Hash' )
}, 'DBM::Deep::Hash' );
All of that looks to be as it should

I need to convert a Json file into a readable csv

I got this Perl code that is supposed to read my categories and put them into a csv file. After many tries i finally got it but is only ready 50 of my over 500 categories. Any way to modify this routine to read all my categories.
Here is the Perl file I got from the Bigcommerce forum.
use strict;
use JSON::PP;
open (my $fh, "<", 'categories.json');
my $json_text = <$fh>;
my $perl_scalar = decode_json($json_text);
# Make a list of ids to names, so that I can build a content path for Neto category CSV
my $id;
foreach my $element (#$perl_scalar)
{
$id->{$element->{id}}=$element->{name};
}
# Actually print out the CSV content, in Neto's required format.
print "content type,content path,name,description 1,description 2,sort order,seo meta description,seo page title,seo meta keywords\n";
foreach my $element (#$perl_scalar)
{
print "Product Category,";
my $parent_category = $element->{parent_category_list}[0];
if ($parent_category == $element->{id})
{
print ",";
}
else
{
print $id->{$parent_category}, ",";
}
print $element->{name}, ",", $element->{description}, ",,", $element->{sort_order}, ",", $element->{meta_description}, ",,\n";
}
Thanks in advance
There is a pretty fundamental problem with mapping JSON to CSV. JSON is a nested data structure, where CSV isn't. Therefore you'll always have to mess around with converting - how would you colliminate:
{
"data2" : {
"fish" : "paste"
},
"data" : [
{
"somesub" : "somethingelse"
},
{
"somesub" : "anotherthing"
}
]
}
This won't turn into a flat data structure like CSV easily.
If you've some trivial JSON to convert, it's not too hard, but depends entirely on the structure of your JSON file, and how you want to map things.
For a trivial example:
use strict;
use warnings;
use JSON;
use Data::Dumper;
local $/;
my $data = from_json(<DATA>);
print Dumper $data;
my #columns = qw ( col1 col2 col3 );
print join( ",", "key", #columns ), "\n";
foreach my $key ( sort keys %$data ) {
print join( ",", $key, #{ $data->{$key} }{#columns} ), "\n";
}
__DATA__
{
"1" :
{
"col1" : "value1",
"col2" : "value2",
"col3" : "value3"
},
"2" : {
"col1" : "value4",
"col2" : "value5",
"col3" : "value6"
}
}
For a more complex example - it may be appropriate to use Text::CSV - but it depends rather what's in your JSON content - the simplistic join approach above doesn't cope with line feeds, embedded quotes or commas within the text. So it might be better to use Text::CSV:
#!/usr/bin/env perl
use strict;
use warnings;
use JSON;
use Text::CSV;
use Data::Dumper;
local $/;
my $data = from_json ( <DATA> );
print Dumper $data;
my $csv = Text::CSV -> new ( { 'binary' => 1 } );
my #columns = qw ( col1 col2 col3 );
$csv -> column_names ( #columns );
foreach my $key ( sort keys %$data ) {
$csv -> print_hr ( \*STDOUT, $data->{$key} );
print "\n";
}
foreach my $key ( sort keys %$data ) {
my $row = [ $key, #{$data->{$key}}{#columns} ];
$csv -> print ( \*STDOUT, $row );
print "\n";
}
This uses the same __DATA__ block as above, and also runs twice - once with using 'column headings' to print - which works provided you don't want to preserve the "key" field, and the second which assembles an array reference to print.

JSON Data output

Below is the Perl code having JSON data:
use Data::Dumper;
use JSON;
my $var = '{
"episode1": {
"title":"Cartman Gets an Anal Probe",
"id":"103511",
"airdate":"08.13.97",
"episodenumber":"101",
"available":"true",
"when":"08.13.97"
}
},
{
"episode2": {
"title":"Weight Gain 4000",
"id":"103516",
"airdate":"08.20.97",
"episodenumber":"102",
"available":"true",
"when":"08.20.97"
}
}';
my $resp = JSON::jsonToObj( $var );
print Dumper ($resp);
The output is:
$VAR1 = {
'episode1' => {
'when' => '08.13.97',
'episodenumber' => '101',
'airdate' => '08.13.97',
'title' => 'Cartman Gets an Anal Probe',
'id' => '103511',
'available' => 'true'
}
};
I am dumping a JSON data but only episode1 is dumped in the output. But, I want both episode1 and episode2 to be displayed when I dump. How to do it?
Write valid JSON.
From JSON Lint
Parse error on line 14:
...: "08.13.97" }},{ "episode2":
---------------------^
Expecting 'EOF'
If you want an array of objects, you need an array in the data: [...].