开发者

Is there a simple way to validate a hash of hash element exists and is defined?

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 ha开发者_运维技巧ve 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:

  • perldoc -f exists
  • perldoc perldata
  • perldoc perldsc

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.

  • It only handles nested hashes. Perl lets you construct arbitrary data structures, like a hash of arrays of scalar references...
  • It doesn't support blessed references (i.e. objects).

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...
}


Great! Thanks you all for the reply.

Since the autovivifying is an issue for me, currently i am using the "awkward" approach, i.e. if (exists $Table{$key1} && defined $Table{$key1}{$key2}) {

Do whatever

}

It works for me, however as you guys said, i have 3-4 level deep of nested hash, the code is bit of messy.

I will check out Data:Diver. That one looks nicer.

Thanks, again,

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜