Access hash reference data using strings representing the hash structure
Let's assume that I have a complex hash reference $hash_ref, and I would like to access data in it by doin开发者_开发百科g something like this:
my $string1 = "{books}";
my $string2 = "{31335}->{book_name}";
print Dumper($hash_ref->$string1->$string2);
Of course, this doesn't work, but I hope it explains what I'd like to do.
Obviously, there are many ways I can make this work, but I am (out of curiosity) really interested to figure out if there is some Perl magic that could make this work without splitting strings, etc.
I know that I could create 3 strings ("books", "31335", "book_name")
and have this done in a second, and there are certainly other ways, but I never understood if one could actually access hash data by using strings that represent hash structure, like in the above example.
Thanks :)
It can be done using eval
. However, just because some can be done doesn't mean it should.
use strict;
use warnings;
my $hr = { books => { 31335 => { book_name => 'FOO' } } };
my $k1 = "{books}";
my $k2 = "{31335}->{book_name}";
my $f = eval "\$hr->$k1->$k2"; # Don't do this. It's a terrible idea.
print $f, "\n"; # FOO
You should bite the bullet and extract the keys from the strings:
my @ks = "$k1$k2" =~ /\{ \s* (.+?) \s* \}/gx;
$f = $hr;
$f = $f->{$_} for @ks;
print $f, "\n"; # FOO
I am in no way saying that this is a good idea, but here is how to do it (without eval
):
use strict;
use warnings;
my $hash_ref = {books => {31335 => {book_name => 'perl'}}};
my $key = sub {
my $hash = shift;
my @keys = grep {s!^\{|\}$!!g; $_} split /->/ => "@_";
$hash = $$hash{$_} for @keys;
$hash
};
my $string1 = "{books}";
my $string2 = "{31335}->{book_name}";
print $hash_ref->$key($string1)->$key($string2); # prints 'perl'
or, to keep the calling code a little cleaner, you can code up a boxing class to handle arbitrary strings as method calls:
sub key_methods {bless \$_[0] => 'KeyMethods'}
{package KeyMethods;
use overload nomethod => sub {${$_[0]}}, # automatic unboxing
'%{}' => sub {${$_[0]}};
sub AUTOLOAD {
my $ret = ${$_[0]}->$key(our $AUTOLOAD =~ /([^:]+)$/);
ref $ret eq 'HASH'
? bless \$ret
: $ret;
}
}
print key_methods($hash_ref)->$string1->$string2; # prints 'perl'
If you are more interested in having a variable store a 'path' to use in accessing a data structure, rather than being able to use a string that can be user submitted or dynamically generated, then you can use an lvalue anonymous sub.
my $deref1 = sub :lvalue { $_[0]->{books} };
my $deref2 = sub :lvalue { shift->{31335}{book_name} }; # if you don't like $_[0]
my $hash_ref = { };
# :lvalue on the sub allow it to be assigned to
$hash_ref->$deref1 = { 31335 => { book_name => 'FOO' } };
print $hash_ref->$deref1->$deref2, "\n";
my $string1 = "{books}";
my $string2 = "{31335}->{book_name}";
my $hash_ref = { key1 => { books => { 31335 => { book_name => "gold" }}}};
my $hash_ref_name = "\$hash_ref";
my $str = join("->", $hash_ref_name, "{key1}", $string1, $string2);
print eval $str, "\n"
AFAIK there is no already available thing which can do this,you have to write a wrap up code to make it possible.I think hash implementation and functions are really simple and cool,you can make it work in very less code.
精彩评论