I need to validate a Perl hash of hash element such as $Table{$key1}{$key2}
to exist and be defined. Here is what I do. (I have no idea $key1
even exists)
if
((defined $Table{$key1}) &&
(exists $Table{$key1}) &&
(defined $Table{$key1}{$key2}) &&
(exists $Table{$key1}{$key2}))
{
#do whatever
}
Is there an easier and cleaner way to do it?
You don't need to check each level of the heirarchy: you can just go for the value you care about. exists
doesn't check for definedness, only if the slot in the hash exists (it could exist with an undefined value), so if you care that the value is defined, you would need to call defined
rather than exists. If a value is not defined, it evaluates in boolean context to false, so we can type a little less and reduce your example to:
if ($Table{$key1}{$key2})
{
# do whatever
}
However, if the value in that key is defined but is "false" (numerically evaluates to zero, or is the empty string), this can cause a false negative, so we should explicitly check for definedness if this is a possibility:
if (defined $Table{$key1}{$key2})
{
# do whatever
}
If you don't want to autovivify $Table{$key1}
, you can check for its existence first, which brings us to the "best" way for the general case:
if (exists $Table{$key1} and defined $Table{$key1}{$key2})
{
# do whatever
}
If you're going to do this a lot for various fields in a hash, you may want to add some OO-style accessor methods which would do this work for you:
sub has_field
{
my ($this, $fieldName) = @_;
return exists $this->{data} && defined $this->{data}{$fieldName});
}
I'm sure you've read it already, but it can't hurt to read the relevant documentation again:
Given an expression that specifies a hash element or array element,
exists
returns true if the specified element in the hash or array has ever been initialized, even if the corresponding value is undefined. The element is not autovivified if it doesn't exist.
...
A hash or array element can be true only if it's defined, and defined if it exists, but the reverse doesn't necessarily hold true.
The following is shorter and will protect from autovivifcation:
if (exists $table{$key1} and defined $table{$key1}{$key2}) {...}
The other checks in your code are not needed.
Check existence first, then defined-ness. (A value can exist without being defined but not be defined without existing.) You should test the intermediate levels with exists
to prevent unintended autovivification. For the last level you only need to call defined
. When there aren't too many layers it's easy to code directly:
if (exists $hash{a} && defined $hash{a}{b}) {...}
This gets awkward if there are many layers:
if (exists $hash{a} && exists $hash{a}{b} && exists $hash{a}{b}{c} ...) {...}
In that case, you can write a version of defined
that doesn't autovivify intermediate values:
sub safe_defined {
my $h = shift;
foreach my $k (@_) {
if (ref $h eq ref {}) {
return unless exists $h->{$k};
$h = $h->{$k};
}
else {
return;
}
}
return defined $h;
}
You use it this way:
if (safe_defined(\%hash, qw(a b c))) {
say $hash{a}{b}{c};
}
Note: This version of the function is limited.
A truly generic version is left as an exercise for the reader. ;)
You could check out Data::Diver. It dives into data structures without autovivifying. The syntax would be:
if ( defined Dive(\%Table, $key1, $key2) ) { ... }
or even:
if ( defined(my $value = Dive(\%Table, $key1, $key2) ) ) {
...do something with $value...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With