Question

I have an array

my @cols = ("accountid", "balance");

and a dataset

my $rowsref=$dbh->selectall_arrayref($_[0]);
foreach my $row (@$rowsref) {
    print join(", ", map {defined $_ ? $_ : "(null)"} @$row), "\n";
}

which prints "1, 150".

I would like to get a JSON output like [{"accountid": 1, "balance": 150},{..}].

I have the JSON module loaded, but unsure how to merge @cols with each $row.

Was it helpful?

Solution

edit: added explanation of column name transaction in 2nd example
edit: Fixed for your requirement of a single JSON encoding of the whole resultset.
edit: forgot keys in cols mapping code in 2nd example.
edit: typo'd hashref everywhere to arrayref. :-

Firstly, use selectall_hashref instead, so that it already contains the key names. Then use one of the JSON encoding modules to encode each row.

(making the same assumptions as your code....)

Using the list-of-hashrefs from selectall_hashref() as-is:

use JSON::XS;
use 5.10.0;
my $rowsref = $dbh->selectall_hashref($_[0]);
print JSON::XS::encode_json($rowsref),"\n";

Performing translation on colnames from selectall_hashref():

If the column names from the database aren't the same as your column names, then you'll need a mapping:

use JSON::XS;
use 5.10.0;
my $trans = { account => 'accountid', amount => 'balance' };
my $rowsref = $dbh->selectall_hashref($_[0]);
my $output = [];
for my $row (@$rowsref) {
    push @$output, {
        map { 
            my $colname = exists($trans->{$_}) ? $trans->{$_} : $_;
            my $value   = $row->{$_};
            $colname => $value;
        } keys %$row                   
    });
}
print JSON::XS::encode_json($output),"\n";

For each $row above of the resultset, keys %$row gives back the column names in the row as returned from the database.

The map operation takes each of those column names and producues 2 scalar values; (1) $colname is either the original database column name or (if it's found in the $trans hashref) a 'translation' of the column name; (2) $value is the value returned by the database for this column name in this particular $row. $colname => $value returns both the $colname and $value from the map as a 'flattened' pair of scalar values. That means that the map operation returns a list of scalar values twice as long as the original list of column names returned by keys %$row.

Finally, the push @$output, { ... } creates an anonymous hash reference from that list of scalar values in key,value,key,value,... order and adds it to the end of the $output array reference.

Blind translation from selectall_arrayref()

If (for some reason) you have a pathological aversion to querying hashrefs from your database, I guess you could do:

use 5.10.0;    
my @cols = ("accountid", "balance");
my $rowsref = $dbh->selectall_arrayref($_[0]);
my $output = [];
for my $row (@$rowsref) {
    my %row = map { $cols[$_] => $row->[$_] } (0 .. $#cols);
    push @$output, \%row; 
}
print JSON::XS::encode_json($output),"\n";

That assumes there are only two columns coming back from the query, though.

"Defined or" operator:

By the way... assuming a late enough perl, you can replace this sort of thing:

defined $_ ? $_ : "(null)"

with this:

$_ // "(null)"

Your code editor (e.g: Vim) might not syntax highlight it correctly if it's not up to date with perl. (e.g: it might treat it as an m// construct).

OTHER TIPS

Note that PostgreSQL can also generate JSON. If it is an option for you then Perl JSON module is redundant.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top