views:

149

answers:

5

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?

+8  A: 

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.

Ether
this will always autovivifiy `$Table{$key1}` if it doesn't exist
Eric Strom
@Eric: see latest edit :)
Ether
I'd make the wrong code stand out more by putting all sorts of comments around it saying "Don't do this!!!!!!!"
brian d foy
@brian: I don't know if I'd call the earlier forms the "wrong" code; simply checking the field for truthiness is good enough in some circumstances (e.g. if autovivication doesn't matter or is not a factor).
Ether
Unintended autovivification is wrong. Don't ignore it and create bad habits that will bite people later.
brian d foy
Autovivification doesn't matter... until it does, and then it's a PITA to debug where the bogus data (that corrupts any time you loop over keys/values) in your hash came from.
Michael Carman
+5  A: 

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.

Eric Strom
+1  A: 

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. ;)

Michael Carman
+1  A: 

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...
}
runrig
A: 

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,

WL