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

Hi Folks,

Sorry my Perl foo is weak and I'm struggling to work out how to solve my issue. I've created a Perl script that collects data from remote hosts and POST's same in JSON format back to a central web server. A Perl cgi script then intercepts and decodes the JSON 'doing stuff' with the output.

Example current JSON:
[ { 'readings' => [ { 'KNOWN1' => '3.28', 'KNOWN2' => '34', 'KNOWN3' => '-73', ... } ], 'FWTYPE' => 'XXX', 'FWID' => '123' } ];

My simple code:

$json = decode_json($POST); # Extract data from JSON for my $report ( @{$json} ) { $DBID = $report->{DBID}; $DISKSIZE = $report->{readings}[0]{DISKSIZE}; $DISKUSED = $report->{readings}[0]{DISKUSED}; $DISKFREE = $report->{readings}[0]{DISKFREE}; ... }

I now want to include IPMITOOL SDR output from various makes of device/servers etc and the issue I have is that the format, name and quantity of the SDR data returned depends heavily on the hardware and its configuration. With my previous code I could rely on constant 'known' key field names however with this new data, field names are only constant from the same device and will change between devices. With this in mind I first need to evaluate the key field names so that I can grab the data and it is this task that I'm unable to wrap my mind around.

Example random field name JSON:
[ { 'readings' => [ { 'asddad' => '123', 'fsfsf' => 'abc', 'sdfsdfsfsf' => 'x1y', ... } ], 'FWTYPE' => 'XXX', 'FWID' => '123' } ];


Any pointers on how to grab 'asddad','fsfsf','sdfsdfsfsf' in the above example into an array that I can then use to query each value would be much appreciated.

Simon

Replies are listed 'Best First'.
Re: Enumerate variable JSON key names/Values
by hippo (Archbishop) on Jan 17, 2024 at 16:10 UTC

    I think you are just asking for this:

    #!/usr/bin/env perl use strict; use warnings; my $json = [ { 'readings' => [ { 'asddad' => '123', 'fsfsf' => 'abc', 'sdfsdfsfsf' => 'x1y', } ], 'FWTYPE' => 'XXX', 'FWID' => '123' } ]; # Keys and values my $hashref = $json->[0]{readings}[0]; for my $k (keys %$hashref) { print "Key '$k' has value '$hashref->{$k}'\n"; } # Just keys my @k = keys %$hashref; print "All keys: @k\n";

    See also perldsc for more on these sorts of nested data structures.


    🦛

Re: Enumerate variable JSON key names/Values
by kcott (Archbishop) on Jan 18, 2024 at 05:53 UTC

    G'day Simon,

    Welcome to the Monastery.

    In case you weren't aware, neither Perl hash key/value pairs nor JSON object property/value pairs are ordered. Any ordering of such data needs to be handled explicitly by your code.

    There seems to be little correlation between your sample data and what you describe. This makes it extremely difficult to provide you with a concrete solution. Instead, I've presented an equivalent problem to the one which I believe is causing you problems; and a solution which I hope will help you with your own issues.

    Different field names from different devices is handled by a lookup table: %device_info.

    Each device gives two sets of readings. These are the same for each device but are referenced by different field names and are all jumbled up (to represent the unordered nature of Perl hashes and JSON objects).

    The output handles the different field names and orders the values so that you can see that the readings are indeed identical.

    I've kept the code very simple and in line with what you posted. If you're unfamiliar with the "@{$readings}{@order}" construct, see "perldata: Slices".

    Here's the code:

    #!/usr/bin/env perl use strict; use warnings; my %device_info = ( DEVA => [qw{a b c}], DEVQ => [qw{q r s}], DEVX => [qw{x y z}], ); my $json = [ { read => [ { qw{r u3 s f1 q s4} }, { qw{q s6 r u4 s f2} }, ], dev => 'DEVQ', rep => 'A', }, { read => [ { qw{y u3 z f1 x s4} }, { qw{x s6 y u4 z f2} }, ], dev => 'DEVX', rep => 'B', }, { read => [ { qw{b u3 c f1 a s4} }, { qw{a s6 b u4 c f2} }, ], dev => 'DEVA', rep => 'C', }, ]; for my $report (@$json) { print "Report $report->{rep} - "; my $device = $report->{dev}; print "Device $device\n"; my @order = @{$device_info{$device}}; for my $readings (@{$report->{read}}) { print "\t@{$readings}{@order}\n"; } }

    Here's the output:

    Report A - Device DEVQ s4 u3 f1 s6 u4 f2 Report B - Device DEVX s4 u3 f1 s6 u4 f2 Report C - Device DEVA s4 u3 f1 s6 u4 f2

    — Ken