Type::Tiny Tricks #7: Tricks with Dicts
In Perl, hashes are typically used for two sorts of purposes: maps (where the hash key acts as an object identifier) and dictionaries (where the hash key acts like a field name). A quick illustration of what I mean by this:
# Maps my %ages = ( alice => 24, bob => 25, carol => 31, ); my %email = ( alice => "alice@example.net", bob => "robert.smith@example.com", carol => "c_jones@example.org", ); # Dictionaries my %alice = ( age => 24, email => "alice@example.net" ); my %bob = ( age => 25, email => "robert.smith@example.com" ); my %carol = ( age => 32, email => "c_jones@example.org" );
These are two different styles of using hashes. Sometimes one is useful, and sometimes the other is. Sometimes neither is better and your choice of one over the other is fairly arbitrary.
Types::Standard contains a type constraint called Map
which is useful for validating references to the first kind of hash. (I copied the idea from MooseX::Types::Structured.) But today I'm mainly going to talk about another type constraint: Dict
. (Yeah, I copied that one too.)
Let's say you have a value which is supposed to be hashref pointing to a hash along the lines of %alice
or %bob
above. We can define a type constraint like this:
use Types::Standard qw( Dict Int ); use Type::EmailAddress qw( EmailAddress ); my $type = Dict[ age => Int, email => EmailAddress ];
And we can check a hashref against the type constraint like this:
$type->check($href) or die "Argghh!!";
Or maybe you can't think of a funny message to die with, so instead you can rely on Type::Tiny to generate one for you.
$type->assert_valid($href); # dies if invalid
Now that's useful, but perhaps sometimes we don't know a person's age. In this case, we can make it optional:
$type = Dict[ age => Optional[Int], email => EmailAddress ];
A hashref with a missing age
key will now pass the type constraint, though a hashref with an explicit undef
in the age slot will fail. Maybe you want to indicate an unknown age with an explicit undef
though. So then you'd use:
$type = Dict[ age => Int|Undef, email => EmailAddress ];
Or perhaps either way is OK. If you don't know the age, sometime you'll provide an explicit undef
, and sometimes you'll just leave the age
key out of the hashref. OK, that's fine too...
$type = Dict[ age => Optional[Int|Undef], email => EmailAddress ];
Here's our last trick. Currently, the following hashref will fail the type constraint check:
$href = { age => 30, email => "dave@example.net", x_website => "http://example.net/~dave/", };
Extra keys are not allowed. This limits the extensibility of our code. But if you read my previous article about tricks with tuples, then you might have an idea about the solution.
use Types::Standard qw( Dict Optional Int slurpy Map StrMatch Str ); use Type::EmailAddress qw( EmailAddress ); my $type = Dict[ age => Optional[Int], email => EmailAddress, slurpy Map[StrMatch[qr/^x_/], Str], ];
OK, this probably requires some explanation. This type constraint will validate age
and email
as usual, but then if the hashref contains any unrecognized fields, these will be slurped up into a temporary hashref (let's call it \%tmp
). It then validates \%tmp
against the Map[StrMatch[qr/^x_/], Str]
type constraint.
What is Map[StrMatch[qr/^x_/], Str]
? It's a hashref where all keys are strings matching qr/^x_/
, and all values are strings. So our hashref of data about Dave should pass OK.
Pop quiz! What does this mean?
$type = Dict[ age => Optional[Int], email => EmailAddress, slurpy Any, ];
Answer: it validates the age
and email
as usual, and slurps any remaining keys into \%tmp
, and validates that against the Any
type constraint.
(In reality this is more optimized. The Any
type constraint always passes, no matter what value you check. So Type::Tiny is smart enough to never actually create that \%tmp
hash.)
The combination of Dict
, Tuple
, Map
, HashRef
, and ArrayRef
allows for some very precise deep validation of values, but remember not to get carried away. A simple:
isa => HashRef
... will run much faster if you're not in need of all that fancy validation!
Leave a comment