Simon. Simon. - 7 days ago 6
Perl Question

Perl, JSON parsing values incorrectly

I'm parsing a JSON string that is stored in a database.

{"name":"simon", "age":"23", "height":"tall"}


I'm pulling the data, then decoding. When running the code below, I'm receiving weird 'HASH' values back.

use JSON;

$data = decode_json($row->{'address'});
for my $key (keys %$data){
if($data->{$key} ne ''){
$XML .= " <$key>$data->{$key}</$key>";
}
}

// Returns data like so

<company_type>HASH(0x27dbac0)</company_type>
<county>HASH(0x27db7c0)</county>
<address1>HASH(0x27dba90)</address1>
<company_name>HASH(0x27db808)</company_name>


The Error happens when I have a data set like so:

{"name":"", "age":{}, "height":{}}


I don't understand why JSON / Arrays / Hashes have to be so difficult to work with in Perl. What point am I missing?

Answer

You are processing a flat hash, while your data in fact has another, nested, hashref. In the line

{ "name":"", "age":{}, "height":{} }

the {} may be intended to mean "nothing" but are in fact JSON "object", the next level of nested data (which are indeed empty). In Perl we get a hashref for it and that's what your code prints.

The other pillar of JSON is an "array" and in Perl we get an arrayref. And that's that -- decode_json gives us back the top-level hashref, which when dereferenced into a hash may contain further hash or array references as values. If you print the whole structure with Data::Dumper you'll see that.

To negotiate this we have to test each time for a reference. Since a dereferenced hash or array may contain yet further levels (more references), we need to use either a recursive routine (see this post for an example) or a module for complex data structures. But for the first level

for my $key (keys %$data)
{
    next if $data->{$key} eq '';

    my $ref_type = ref $data->{$key};

    # if $data->{key} is not a reference ref() returns an empty string (false)
    if (not $ref_type) {
        $XML .= "      <$key>$data->{$key}</$key>";
    }
    elsif ($ref_type eq 'HASH') {
        # hashref,  unpack and parse. it may contain references
        say "$_ => $data->{$key}{$_}" for keys %{ $data->{$key} };
    }
    elsif ($ref_type eq 'ARRAY') {
        # arrayref, unpack and parse. it may contain references
        say "@{$data->{$key}}";
    }
    else { say "Reference is to type: $ref_type" }
}

If the argument of ref is not a reference (but a string or a number) it returns an empty string, which evaluates as false, and this is when you have plain data. Otherwise it returns the type the reference is to. Coming from JSON it can be either a HASH or an ARRAY. This is how nesting is accomplished.

In the shown example you are runnig into hashref. Since the ones you show are empty you can just discard them and the code for the specific example can reduce greatly, to one statement. However, I'd leave the other tests in place. This should also work as it stands with the posted example.