Im trying to parse out JSON output from the server using Perl. The connection and downloading of the REST data is ok, I just need help parsing the returned data. Here is the snippet for my code:
my $response = HTTP::Tiny->new->get($SERVER_ADDR);
if ($response->{success})
{
my $html = $response->{content};
#LINES = split /\n/, $html;
chomp(#LINES);
print("Lines: '#LINES'\n"); # ZZZ
my $decoded_json = decode_json( $html );
print Dumper $decoded_json;
}
else
{
print "Failed: $response->{status} $response->{reasons}";
}
And here is the results:
Lines: '{"players":[{"currentlyOnline":false,"timePlayed":160317,"name":"MarisaG","lastPlayed":1474208741470}]}'
$VAR1 = {
'players' => [
{
'currentlyOnline' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
'timePlayed' => 160317,
'lastPlayed' => '1474208741470',
'name' => 'MarisaG'
}
]
};
There will be multiple entries under the "players" for each player logged in right now. Any tips?
I'm not really sure what you're asking. You have successfully parsed the JSON by by calling decode_json(). You now have a data structure in $decoded_json. Your call to Dumper() shows the structure of that data. It's a hash reference with a single key, players. The value associated with that key is an array reference. Each element in the referenced array is another hash.
So, for example, you could print all of the players' names with code like this.
foreach (#{ $decoded_json->{players} }) {
say $_->{name};
}
Related
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.
I have a configuration file which is in XML format. I need to parse the XML and convert to JSON. I'm able to convert it with XML2JSON module of perl. But the problem is, it is not maintaining the order of XML elements. I strictly need the elements in order otherwise I cannot configure
My XML file is something like this. I have to configure an IP address and set that IP as a gateway to certain route.
<Config>
<ip>
<address>1.1.1.1</address>
<netmask>255.255.255.0</netmask>
</ip>
<route>
<network>20.20.20.0</network>
<netmask>55.255.255.0</netmask>
<gateway>1.1.1.1</gateway>
</route>
</Config>
This is my perl code to convert to JSON
my $file = 'config.xml';
use Data::Dumper;
open my $fh, '<',$file or die;
$/ = undef;
my $data = <$fh>;
my $XML = $data;
my $XML2JSON = XML::XML2JSON->new();
my $Obj = $XML2JSON->xml2obj($XML);
print Dumper($Obj);
The output I'm getting is,
$VAR1 = {'Config' => {'route' => {'netmask' => {'$t' => '55.255.255.0'},'gateway' => {'$t' => '1.1.1.1'},'network' => {'$t' => '20.20.20.0'}},'ip' => {'netmask' => {'$t' => '255.255.255.0'},'address' => {'$t' => '1.1.1.1'}}},'#encoding' => 'UTF-8','#version' => '1.0'};
I have a script which reads the json object and configure..
But it fails as it first tries to set gateway ip address to a route where the ip address is not yet configured and add then add ip address.
I strictly want key ip to come first and then route for proper configuration without error. Like this I have many dependencies where order of keys is a must.
Is there any way I can tackle this problem? I tried almost all modules of XML parsing like XML::Simple,Twig::XML,XML::Parser. But nothing helped..
Here's a program that I hacked together that uses XML::Parser to parse some XML data and generate the equivalent JSON in the same order. It ignores any attributes, processing instructions etc. and requires that every XML element must contain either a list of child elements or a text node. Mixing text and elements won't work, and this isn't checked except that the program will die trying to dereference a string
It's intended to be a framework for you to enhance as you require, but works fine as it stands with the XML data you show in your question
use strict;
use warnings 'all';
use XML::Parser;
my $parser = XML::Parser->new(Handlers => {
Start => \&start_tag,
End => \&end_tag,
Char => \&text,
});
my $struct;
my #stack;
$parser->parsefile('config.xml');
print_json($struct->[1]);
sub start_tag {
my $expat = shift;
my ($tag, %attr) = #_;
my $elem = [ $tag => [] ];
if ( $struct ) {
my $content = $stack[-1][1];
push #{ $content }, $elem;
}
else {
$struct = $elem;
}
push #stack, $elem;
}
sub end_tag {
my $expat = shift;
my ($elem) = #_;
die "$elem <=> $stack[-1][0]" unless $stack[-1][0] eq $elem;
for my $content ( $stack[-1][1] ) {
$content = "#$content" unless grep ref, #$content;
}
pop #stack;
}
sub text {
my $expat = shift;
my ($string) = #_;
return unless $string =~ /\S/;
$string =~ s/\A\s+//;
$string =~ s/\s+\z//;
push #{ $stack[-1][1] }, $string;
}
sub print_json {
my ($data, $indent, $comma) = (#_, 0, '');
print "{\n";
for my $i ( 0 .. $#$data ) {
# Note that $data, $indent and $comma are overridden here
# to reflect the inner context
#
my $elem = $data->[$i];
my $comma = $i < $#$data ? ',' : '';
my ($tag, $data) = #$elem;
my $indent = $indent + 1;
printf qq{%s"%s" : }, ' ' x $indent, $tag;
if ( ref $data ) {
print_json($data, $indent, $comma);
}
else {
printf qq{"%s"%s\n}, $data, $comma;
}
}
# $indent and $comma (and $data) are restored here
#
printf "%s}%s\n", ' ' x $indent, $comma;
}
output
{
"ip" : {
"address" : "1.1.1.1",
"netmask" : "255.255.255.0"
},
"route" : {
"network" : "20.20.20.0",
"netmask" : "55.255.255.0",
"gateway" : "1.1.1.1"
}
}
The problem isn't so much to do with XML parsing, but because perl hashes are not ordered. So when you 'write' some JSON... it can be any order.
The way to avoid this is to apply a sort function to your JSON.
You can do this by using sort_by to explicitly sort:
#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;
use JSON::PP;
use Data::Dumper;
sub order_nodes {
my %rank_of = ( ip => 0, route => 1, address => 2, network => 3, netmask => 4, gateway => 5 );
print "$JSON::PP::a <=> $JSON::PP::b\n";
return $rank_of{$JSON::PP::a} <=> $rank_of{$JSON::PP::b};
}
my $twig = XML::Twig -> parse (\*DATA);
my $json = JSON::PP -> new;
$json ->sort_by ( \&order_nodes );
print $json -> encode( $twig -> simplify );
__DATA__
<Config>
<ip>
<address>1.1.1.1</address>
<netmask>255.255.255.0</netmask>
</ip>
<route>
<network>20.20.20.0</network>
<netmask>55.255.255.0</netmask>
<gateway>1.1.1.1</gateway>
</route>
</Config>
In some scenarios, setting canonical can help, as that sets ordering to lexical order. (And means your JSON output would be consistently ordered). This doesn't apply to your case.
You could build the node ordering via XML::Twig, either by an xpath expression, or by using twig_handlers. I gave it a quick go, but got slightly unstuck in figuring out how you'd 'tell' how to figure out ordering based on getting address/netmask and then network/netmask/gateway.
As a simple example you could:
my $count = 0;
foreach my $node ( $twig -> get_xpath ( './*' ) ) {
$rank_of{$node->tag} = $count++ unless $rank_of{$node->tag};
}
print Dumper \%rank_of;
This will ensure ip and route are always the right way around. However it doesn't order the subkeys.
That actually gets a bit more complicated, as you'd need to recurse... and then decide how to handle 'collisions' (like netmask - address comes before, but how does it sort compared to network).
Or alternatively:
my $count = 0;
foreach my $node ( $twig->get_xpath('.//*') ) {
$rank_of{ $node->tag } = $count++ unless $rank_of{ $node->tag };
}
This walks all the nodes, and puts them in order. It doesn't quite work, because netmask appears in both stanzas though.
You get:
{"ip":{"address":"1.1.1.1","netmask":"255.255.255.0"},"route":{"netmask":"55.255.255.0","network":"20.20.20.0","gateway":"1.1.1.1"}}
I couldn't figure out a neat way of collapsing both lists.
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: [...].
I have the following code for exporting all the items in one of my pods to json. The thing is I don't need all the 130 columns in the json file, but only about 20. Since this will be done for about 150 items I thought I could save some loading time by not printing out all the fields, but I do not know how to do this. For example I only want to print the column value named 'title' for all items in the pod. My code is attached bellow.
<?php
$pods = pods('name', array('orderby' => 'name asc', 'limit' => -1));
$all_companies = $pods->export_data();
if ( !empty( $all_companies ) ) {
die(json_encode($all_companies);
}else{
die(json_encode(array('error' => 'No cars found.')));
}
?>
I thought about doing something like this:
if ( 0 < $all_companies->total() ) {
while ($all_companies->fetch()) {
$json .= $all_companies->field('title');
}
$json = rtrim($json, ",");
$json .= '}}';
}
echo $json;
But it doesn't work and also the code becomes very long.
I'd make an array of the names of the twenty fields you want then build an array of those fields for each item, by doing a foreach of those field names passed to Pods::field() inside the while loop. Like this:
$pods = pods('name', array('orderby' => 'name asc', 'limit' => -1));
$fields = array( 'field_1', 'field_2' );
if ( $pods->total() > 0 ) {
while ( $pods->fetch() ) {
foreach ( $fields as $field ) {
$json[ $pods->id() ] = $pods->field( $field );
}
}
$json = json_encode( $json );
}
Alternatively, you could hack the /pods/<pod> endpoint of our JSON API to accept a list of fields to return as the body of the request. Wouldn't be hard to do, make sure to submit a pull request if you make it work.
I have a JSON that prints
{"d":{"success":true,"drivers":[{"FIRST_NAME":"JOHN","LAST_NAME":"SMITH"},{"FIRST_NAME":"JANE","LAST_NAME":"DOE"}]}}
The names change depending on what was found in the database. I need to push this in this format for each result resturned in the JSON:
push(#$dummy_data, {'name' => 'testname', 'key' => 'somekey-1234'});
push(#$dummy_data, {'name' => 'testname2', 'key' => 'somekey-5678'});
So for this example it would be John Smith in place of testname and Jane for testname2
How would I do this so for each first and last name in the json gets pushed in the format above?
Let's try this new game
use strict; use warnings;
use JSON::XS;
use Data::Dumper;
# creating reference to a void ARRAY
my $dummy_data = [];
# creating $json string
my $json = '{"d":{"success":true,"drivers":[{"FIRST_NAME":"JOHN","LAST_NAME":"SMITH"},{"FIRST_NAME":"JANE","LAST_NAME":"DOE"}]}}';
# converting JSON -> Perl data structure
my $perl_hash = decode_json $json;
# feeding $dummy_data ARRAY ref with a HASH
push #$dummy_data, {
name => $perl_hash->{d}->{drivers}->[0]->{FIRST_NAME},
key => $perl_hash->{d}->{drivers}->[1]->{FIRST_NAME}
};
# print what we have finally
print Dumper $dummy_data;
Output
$VAR1 = [
{
'name' => 'JOHN',
'key' => 'JANE'
}
];