Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 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?

like image 896
WL. Avatar asked Apr 27 '10 17:04

WL.


4 Answers

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.

like image 113
Ether Avatar answered Oct 05 '22 00:10

Ether


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.

like image 28
Eric Strom Avatar answered Oct 05 '22 00:10

Eric Strom


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

like image 34
Michael Carman Avatar answered Oct 04 '22 22:10

Michael Carman


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...
}
like image 21
runrig Avatar answered Oct 04 '22 23:10

runrig