Util::H2O(3pm)
β¨ Util::H2O π Hash to Object: turns hashrefs into objects with accessors for keys πΈ
okay bestie, so this is Util::H2O(3pm), straight out of the User Contributed Perl Documentation, and it is lowkey iconic. it's the module that takes your sad little hashrefs and gives them main character energy: real accessors, typo protection, even whole baby classes. let's go π
π NAME π
Util::H2O β Hash to Object: turns hashrefs into objects with accessors for keys
π SYNOPSIS π
use Util::H2O;
my $hash = h2o { foo => "bar", x => "y" }, qw/ more keys /;
print $hash->foo, "\n"; # accessor
$hash->x("z"); # change value
$hash->more("cowbell"); # additional keys
my $struct = { hello => { perl => "world!" } };
h2o -recurse, $struct; # objectify nested hashrefs as well
print $struct->hello->perl, "\n";
my $obj = h2o -meth, { # code references become methods
what => "beans",
cool => sub {
my $self = shift;
print $self->what, "\n";
} };
$obj->cool; # prints "beans"
h2o -classify=>'Point', { # whip up a class
angle => sub { my $self = shift; atan2($self->y, $self->x) }
}, qw/ x y /;
my $one = Point->new(x=>1, y=>2);
my $two = Point->new(x=>3, y=>4);
printf "%.3f\n", $two->angle; # prints 0.927
π¦ DESCRIPTION π¦
so here's the whole vibe: this module turns hashrefs into objects. which means instead of writing $hash->{key} like some kind of gremlin, you get to write $hash->key, and the universe (okay, Perl) yells at you if you typo it. obsessed. and as a lil bonus, there are options that let you whip up really simple classes out of thin air β¨
and the best part? it's not either/or, babe. you can still use the hash like a totally normal hashref β $hash->{key}, keys %$hash, all of that β but heads up, by default this function also locks the hash's keyset to prevent typos there too π
this module exports a single function by default.
π
h2o @opts, $hashref, @additional_keys π
β @opts β
if you specify an option with a value multiple times, only the last one takes effect. (last one wins, no cap.)
-recurseβ nested hashes get objectified too πͺ the only options that get passed down to the nested hashes are-lockand-ro. none of the other options get applied to the nested hashes, and that includes@additional_keys. nested arrayrefs are not recursed into, but check the-arraysoption if that's your thing.- versions of this module before v0.12 did not pass down the
-lockoption, which means if you used-nolock, -recurseon those versions, the nested hashes would still be locked. rude, honestly.
- versions of this module before v0.12 did not pass down the
-arraysβ like-recurse, but additionally,h2ogets applied to elements of nested arrays as well. the same options as with-recurseget passed down to nested hashes and arrayrefs. takes precedence over the-passoption, i.e. if you use these two options together, arrayrefs are still descended into. and like hashrefs, the original arrays are modified! π³ this option implies-recurse. this option was added in v0.20.-methβ any code references present in the hash at the time of this function call get turned into methods. because these methods are installed into the object's package, they can't be changed later by modifying the hash. to avoid confusion when iterating over the hash, the hash entries that were turned into methods are removed from the hash. the key is also removed from the "allowed keys" (see the-lockoption), unless you specify it in@additional_keys. in that case, you can change the value of that key completely independently of the method with the same name. slay π-class => classnameβ specify the class name to bless the object into (as opposed to the default: a generated, unique package name inUtil::H2O::).- note: if you use this option,
-cleandefaults to false, which means the package will stay in Perl's symbol table and use memory accordingly, and since this function installs the accessors in the package every time it is called, if you re-use the same package name, you will getredefinedwarnings. so if you want to create multiple objects in the same package, you should probably use-newor-classify. - if you wanted to generate a unique package name in a different package, you could use:
h2o -class => sprintf('My::Class::Name::_%x', $hash+0), $hash, maybe even in combination with-isa => 'My::Class::Name'. however, keep in mind that you shouldn't step into another class' namespace without knowing that this won't cause conflicts, and also that not using the default class names means that functions likeo2hwill no longer identify the objects as coming fromh2o. π
- note: if you use this option,
-classify => classname_string or $hashrefβ in the form-classify => classname_string, this is simply the short form of the options-new, -meth, -class => classname_string.- as of v0.16, in the special form
-classify => $hashref, where the-classifymust be the last option in@optsbefore the$hashref, it's the same as-new, -meth, -class => __PACKAGE__, $hashrefβ that is, the current package's name is used as the custom class name. it does not make sense to use this outside of an explicit package, since your class will be namedmain. with this option, thePointexample in the "Synopsis" can be written like the following, which can be useful if you want to add more things to thepackage, or maybe if you want to write your methods as regularsubs:
- as of v0.16, in the special form
{
package Point;
use Util::H2O;
h2o -classify, {
angle => sub { my $self = shift; atan2($self->y, $self->x) }
}, qw/ x y /;
}
- note
h2owill remain in the package's namespace, and one possibility is that you could loadnamespace::cleanafter you load this module. - you might also note that in the above example, one could write
angleas a regularsubin the package. and at that point, one might recognize the similarity between the code and what one can do with e.g. Class::Tiny or even Moo. π
-isa => arrayref or scalarβ convenience option to set the@ISAvariable in the package of the object, so that the object inherits from that/those package(s). this option was added in v0.14.
warning: the methods created byh2owill not call superclass methods. this means the parent class'DESTROYmethod(s) are not called, and any accessors generated from hash keys are blindly overriden. be careful bestie π
-newβ generates a constructor namednewin the package. the constructor works as a class and instance method, and dies if it is given any arguments that it doesn't know about. if you want more advanced features, like required arguments, validation, or other initialization, you should probably switch to something like Moo instead.-destroy => coderefβ allows you to specify a custom destructor. this coderef will be called from the object's actualDESTROYin void context with the first argument being the same as the first argument to theDESTROYmethod. errors will be converted to warnings. this option was added in v0.14.-clean => boolβ whether or not to clean up the generated package when the object is destroyed. defaults to false when-classis specified, true otherwise. if this is false, be aware that the packages will stay in Perl's symbol table and use memory accordingly, and any subs/methods in those packages may causeredefinedwarnings if the package name is re-used.- as of v0.16, this module will refuse to delete the package if it is named
main.
- as of v0.16, this module will refuse to delete the package if it is named
-lock => boolβ whether or not to use Hash::Util'slock_ref_keysto prevent modifications to the hash's keyset. defaults to true. the-nolockoption is provided as a short form of-lock=>0.- keysets of objects created by the constructor generated by the
-newoption are also locked. versions of this module before v0.12 did not lock the keysets of new objects. - note that on really old Perls, that is, before Perl v5.8.9, Hash::Util and its
lock_ref_keysare not available, so the hash is never locked on those versions of Perl. versions of this module before v0.06 did not lock the keyset. versions of this module as of v0.12 issue a warning on old Perls.
- keysets of objects created by the constructor generated by the
-nolockβ short form of the option-lock=>0.-roβ makes the entire hash read-only using Hash::Util'slock_hashref, and the generated accessors will also throw an error if you try to change values. in other words, this makes the object and the underlying hash immutable. π§- you cannot specify any
@additional_keyswith this option enabled unless you also use the-newoption β the additional keys will then only be useful as arguments to the constructor. this option can't be used with-nolockor-lock=>0. - this option was added in v0.12. using this option will not work and cause a warning when used on really old Perls (before v5.8.9), because this functionality was not yet available there.
- you cannot specify any
-pass => "ref" or "undef"β when this option is set toundef(that's the string"undef", notundefitself!), then passing a value ofundeffor the$hashrefwill not result in a fatal error, the value will simply be passed through.- when this option is set to the string
"ref", then any value other than a plain hashref that is a reference, including objects, plusundefas above, will be passed through without modification. any hashes nested inside of these references will not be descended into, even when-recurseis specified. however,-arraystakes precedence over this option, see its documentation. - this option was added in v0.18.
- when this option is set to the string
πΈ $hashref πΈ
you must supply a plain (unblessed) hash reference here, unless you've specified the -pass and/or -arrays options. be aware that this function does modify the original hashref(s) by blessing it and locking its keyset (the latter can be disabled with the -lock option), and if you use -meth or -classify, keys whose values are code references will be removed. if you use -arrays, the elements of those arrays may also be modified. π
an accessor will be set up for each key in the hash(es); note that the keys must of course be valid Perl identifiers for you to be able to call the method normally (see also the "Cookbook").
the following keys will be treated specially by this module. please note that there are further keys that are treated specially by Perl and/or that other code may expect to be special, such as UNIVERSAL's isa. see also perlsub and the references therein.
newβ this key is not allowed in the hash if the-newoption is on.DESTROYβ this key is not allowed except if all of the following apply:
So DESTROY is only allowed when:
-destroyis not used,-cleanis off (which lowkey happens by default when you use-class),-methis on, and- the value of the key
DESTROYis a coderef.
Versions of this module before v0.14 allowed a DESTROY key in more circumstances (whenever -clean was off).
β¨ AUTOLOAD β¨
If your hash contains a key named AUTOLOAD, or this key is present in @additional_keys, this module will set up a method called AUTOLOAD, which is subject to Perl's normal autoloading behavior β see "Autoloading" in perlsub and "AUTOLOAD" in perlobj. Without the -meth option, you will get a "catch-all" accessor to which all method calls to unknown method names will go, and with -meth enabled (which is implied by -classify), you can install your own custom AUTOLOAD handler by passing a coderef as the value for this key β see "An Autoloading Example". However, it is important to note that enabling autoloading removes any typo protection on method names! π±
πΈ @additional_keys πΈ
Methods will be set up for these keys even if they do not exist in the hash.
Please see the list of keys that are treated specially above.
π Returns π
The (now blessed and optionally locked) $hashref.
π§ o2h @opts, $h2object π§
This function takes an object as created by h2o and turns it back into a hashref by making shallow copies of the object hash and any nested objects that may have been created via -recurse, -arrays, or created manually. This function is recursive by default because for a non-recursive operation you can simply write: {%$h2object} (making a shallow copy).
Unlike h2o, this function returns a new hashref instead of modifying the given variable in place (unless what you give this function is not an h2o object, in which case it will just be returned unchanged). Similarly, if you specify the -arrays option, shallow copies of arrays will be returned in place of the original ones, with o2h applied to the elements.
Note that this function operates only on objects in the default package β it does not step into plain hashrefs, it does not step into arrayrefs unless you specify -arrays, nor does it operate on objects created with the -class or -classify options. Also be aware that because methods created via -meth are removed from the object hash, these will disappear in the resulting hashref.
This function was added in v0.18.
β¨ @opts β¨
If you specify an option with a value multiple times, only the last one will take effect.
-arraysβ If you specify this option, nested arrayrefs are descended into as well.
This option was added in v0.20.
--β This string ends the option processing, allowing you to pass scalar values too2hthat would otherwise be interpreted as options.
The o2h function is special-cased such that a call o2h("--") returns -- instead of throwing an error.
This was added in v0.24 in order to fix a bug with scalars beginning with - in earlier versions of this module. Users of o2h are advised to upgrade.
π³ Cookbook π³
π Keys with Spaces, Dashes, or Other Non-Identifier Characters π
If the hash you want to pass to h2o contains keys that are not usable as method names, such as keys containing spaces or dashes, you can transform the hash before passing it to h2o. There are several ways to achieve this, including in plain Perl, but one of the easier ways is with pairmap from the core module List::Util.
use List::Util 'pairmap';
my $hash = { "foo bar" => 123, "quz-ba%z" => 456 };
my $obj = h2o { pairmap { $a=~tr/a-zA-Z0-9/_/c; ($a,$b) } %$hash };
print $obj->foo_bar, $obj->quz_ba_z, "\n"; # prints "123456"
πΈ Using with Config::Tiny πΈ
One common use case for this module is to make accessing hashes nicer, like for example those you get from Config::Tiny. Here's how you can create a new h2o object from a configuration file:
use Util::H2O 0.24 qw/ h2o o2h /; # v0.24 for o2h (with bugfixes)
use Config::Tiny 2.27; # v2.27 for writing file back out
my $config = h2o -recurse, {%{ Config::Tiny->read($config_filename) }};
say $config->foo->bar; # prints the value of "bar" in section "[foo]"
$config->foo->bar("Hello, World!"); # change value
# write file back out
Config::Tiny->new(o2h $config)->write($config_filename);
π Debugging π
Because the packages generated by h2o are dynamic, note that any debugging dumps of these objects will be somewhat incomplete because they won't show the methods. However, if you'd like somewhat nicer looking dumps of the data contained in the objects, one way you can do that is with Data::Dump::Filtered:
use Util::H2O;
use Data::Dump qw/dd/;
use Data::Dump::Filtered qw/add_dump_filter/;
add_dump_filter( sub {
my ($ctx, $obj) = @_;
return { bless=>'', comment=>'Util::H2O::h2o()' }
if $ctx->class=~/^Util::H2O::/;
return undef; # normal Data::Dump processing for all other objects
});
my $x = h2o -recurse, { foo => "bar", quz => { abc => 123 } };
dd $x;
Outputs:
# Util::H2O::h2o()
{
foo => "bar",
quz => # Util::H2O::h2o()
{ abc => 123 },
}
π¦ An Autoloading Example π¦
If you wanted to create a class where (almost!) every method call is automatically translated to a hash access of the corresponding key, here's how you could do that:
h2o -classify=>'HashLikeObj', -nolock, {
AUTOLOAD => sub {
my $self = shift;
our $AUTOLOAD;
( my $key = $AUTOLOAD ) =~ s/.*:://;
$self->{$key} = shift if @_;
return $self->{$key};
} };
π Upgrading to Moo π
Let's say you've used this module to whip up two simple classes:
h2o -classify => 'My::Class', {}, qw/ foo bar details /;
h2o -classify => 'My::Class::Details', {}, qw/ a b /;
But now you need more features and would like to upgrade to an actual OO system like Moo. Here's how you'd write the above code using that, with some Type::Tiny thrown in:
package My::Class2 {
use Moo;
use Types::Standard qw/ InstanceOf /;
use namespace::clean; # optional but recommended
has foo => (is=>'rw');
has bar => (is=>'rw');
has details => (is=>'rw', isa=>InstanceOf['My::Class2::Details']);
}
package My::Class2::Details {
use Moo;
use namespace::clean;
has a => (is=>'rw');
has b => (is=>'rw');
}
π See Also π
Inspired in part by lock_keys from Hash::Util.
Many, many other modules exist to simplify object creation in Perl. This one is mine ;-P
Similar modules include Object::Adhoc, Object::Anon, Hash::AsObject, Object::Result, and Hash::Wrap, the latter of which also contains a comprehensive list of similar modules. Also, see Class::Tiny for another minimalistic class generation module.
For real OO work, I like Moo and Type::Tiny (see "Upgrading to Moo").
Further modules that might be useful in combination with this one: Hash::Merge for merging hashes before using this module (for example, to supply default values for keys); Role::Tiny for applying roles.
See also Util::H2O::More by OODLER, a module with additional functionality on top of this module.
π Special Thanks π
Thanks to oodler577 on GitHub (OODLER on CPAN), whose many suggestions have inspired a lot of the features in this module!
π Author, Copyright, and License π
Copyright (c) 2020-2023 Hauke Daempfling (haukex@zero-g.net).
This library is free software; you can redistribute it and/or modify it under the same terms as Perl 5 itself.
For more information see the Perl Artistic License, which should have been distributed with your copy of Perl. Try the command perldoc perlartistic or see http://perldoc.perl.org/perlartistic.html.
perl v5.36.0 β’ 2023-12-17 β’ Util::H2O(3pm) π β¨
π¦ where this page comes from
- debian trixie: libutil-h2o-perl 0.24-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libutil-h2o-perl 0.24-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libutil-h2o-perl 0.24-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libutil-h2o-perl 0.24-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms