Search code examples
arraysperlhashperl-data-structures

Perl: Format a hash reference (e.g. output of Dumper) into a String


I have a hash reference that contains nested key/value pairs, hash references and/or array references.

I'd like to emulate the result of Data::Dumper into a string, but with:

  1. Remove 'quotes' from keys.
  2. Remove white spaces from structures (but not values)
  3. Bonus: sort keys alphabetically.
  4. Bonus Bonus: print key/value pairs first, followed by hash references, followed by array references.

For example:

#!/usr/bin/perl -w
use strict;
use warnings;

use Data::Dumper;

my $hash_ref = {
    'private' => {
        'locked' => 'FALSE',
        'allowedAuth' => 'Digest'
    },
    'profile' => 'Default',
    'id' => '123456',
    'privacy' => 'FALSE',
    'public' => [
    {
        'allowed' => 'FALSE',
        'configured' => {
            'profileId' => 'hello world'
        },
        'isDefault' => 'TRUE',
        'maxSessions' => '3'
    },
    {
        'isDefault' => 'FALSE',
        'privateId' => 'foo@bar.com',
        'maxSessions' => '3',
        'allowed' => 'FALSE',
        'implicit' => '1',
    }
    ],
    'indicator' => 'FALSE'
};

print STDERR Dumper ($hash_ref);

Ideally, I'd like the output to be:

my $str = "id=>'123456',indicator=>'FALSE',profile=>'Default',privacy=>'FALSE',private=>{allowedAuth=>'Digest',locked=>'FALSE'},public=>[{allowed=>'FALSE',configured=>{profileId=>'hello world'},isDefault=>'TRUE',maxSessions=>'3'},{allowed=>'FALSE',implicit=>'1',isDefault=>'FALSE',maxSessions=>'3',privateId=>'foo@bar.com'}]";

I've attempted a recursive function; however, I'm not sure how to get rid of the commas at the end (especially of hash references - for array ref I can use an index and check if it's the last one). Also, sorting keys seems too difficult.

sub recHash
{
    my ($hash_ref) = @_;
    my $response = "";
    for my $k (keys %$hash_ref) {
    my $v = $hash_ref->{$k};
    if (ref($v) eq "HASH") {
        $response .= "$k=>{" . recHash($v) . "}"; # recurse through the hash references.
    }
    elsif (ref($v) eq "ARRAY") {
        $response .= "$k=>[";
        # recurse through the array references.
        foreach my $item (@$v) {
        $response .= "{".recHash($item)."},";
        }
        $response .= "],";
        return $response;
    }
    else {
        $response .= "$k=>'$v',";
    }
    }
    return $response;
}

print recHash($hash_ref);

My output is (which I think is flawed when I keep running it):

private=>{allowedAuth=>'Digest',locked=>'FALSE',}profile=>'Default',id=>'123456',indicator=>'FALSE',privacy=>'FALSE',public=>[{configured=>{profileId=>'hello world',}maxSessions=>'3',allowed=>'FALSE',isDefault=>'TRUE',},{allowed=>'FALSE',maxSessions=>'3',implicit=>'1',privateId=>'foo@bar.com',isDefault=>'FALSE',},],

Solution

  • Out-of-the-box $Data::Dumper::Indent and $Data::Dumper::Sortkeys values will get you most of the way there.

    use Data::Dumper;
    my $hash_ref = { ... };
    
    $Data::Dumper::Indent = 0;
    $Data::Dumper::Sortkeys = sub {
        my ($hash) = @_;
        my %refval = ('' => -3, 'HASH' => -2, 'ARRAY' => -1);
        return [ sort {
             # prefer ref(val) "" to "HASH" to "ARRAY" to anything else
                      $refval{ref $hash->{$a}} <=> $refval{ref $hash->{$b}}
             # and then sort lexicographically
                      || $a cmp $b 
                 } keys %$hash ];
    };
    my $rec_hash = Dumper($hash_ref);
    $rec_hash =~ s/'(\w+)' => /$1=>/g;
    $rec_hash =~ s/^\$VAR1 = //;
    print $rec_hash;
    

    Result:

    {id=>'123456',indicator=>'FALSE',privacy=>'FALSE',profile=>'Default',
     private=>{allowedAuth=>'Digest',locked=>'FALSE'},public=>
     [{allowed=>'FALSE',isDefault=>'TRUE',maxSessions=>'3',configured=>
     {profileId=>'hello world'}},allowed=>'FALSE',implicit=>'1',
     isDefault=>'FALSE',maxSessions=>'3',privateId=>'foo@bar.com'}]};