Object as hash key
Asked Answered
R

3

15

Is it possible to use an object as a hash key?

For example, the following code allows me to use an instance of MyClass as a key but when I iterate over the keys and attempt to invoke the get_value method, I get the error:

Can't locate object method "get_value" via package "MyClass=HASH(0x12a4040)" (perhaps you forgot to load "MyClass=HASH(0x12a4040)"?)

package MyClass;
use strict;

sub new
{
    my $class = shift;
    my $self = {
        _value => shift
    };
    bless $self, $class;
    return $self;
}

sub get_value {
    my($self) = @_;
    return $self->{_value};
}

my %hash = ();
%hash->{new MyClass(1)} = 0;
%hash->{new MyClass(2)} = 1;

for my $key (keys %hash)
{
    print $key->get_value;
}
Rauscher answered 13/8, 2010 at 18:24 Comment(0)
P
20

By default, all hash keys in Perl are strings, so what is happening in you code (which has other problems as well), is you are converting the object to a string and storing the string.

In general, if you want to use a object as a key, the simplest way to do it is to use two data structures, one that holds your objects (an array), and another that maps the objects to some values (a hash). It is also possible to create a tied hash which will support objects as keys, but in general, tied hashes are going to be slower than simply using two data structures.

The standard module Tie::RefHash provides a mechanism for using objects (and other references) as hash keys (that work properly when you get them back).

use Tie::RefHash;
tie my %hash, 'Tie::RefHash';

$hash{MyClass->new(1)} = 0;  # never use the indirect object syntax
....
Provoke answered 13/8, 2010 at 18:45 Comment(0)
C
9

Anything used as a hash key is stringified. So when using your object as a hash key, you're only getting a string representation of it and not the actual object itself.

The real question is, why in the world would you want to do this?

Also, the syntax for assigning values to a hash is $hash{key} = $val; the arrow is used when you're dealing with a hash reference.

If you want to associate objects with some other value, one way would be to use an array of hashes, e.g.

my @foo;
push @foo, { obj => MyClass->new( 1 ), val => 0 };
push @foo, { obj => MyClass->new( 2 ), val => 1 };

Then you could call $foo[0]{obj}->get_value();

If you just want your objects to be able to return some unique per-instance ID, you could add a method that takes advantage of Scalar::Util's refaddr operator:

use Scalar::Util 'refaddr';

sub unique_id { 
    my $self = shift;
    return refaddr $self;
}

...

$hash{MyClass->new(1)->unique_id} = 0;

For more: perlobj, perldata, perlreftut, Scalar::Util

Chimb answered 13/8, 2010 at 18:42 Comment(0)
B
1

For those hashing on objects serialized with JSON using convert_blessed:

We were serializing objects that stringify using JSON's TO_JSON hook and returned an object of hashes:

package FromAbove;

sub new { 
  my ($class, %args) = @_;
  return bless(\%args, $class)
}

sub TO_JSON {
  my $self = shift;
  return { %$self };
}
1;

and later, using JSON::to_json to stringify:

my $object_from_above = FromAbove->new( a => { b => 'c'}, d => [1, 2, 3] );
my $json_string = JSON::to_json($object_from_above, { convert_blessed => 1 });

Perl was inconsistently re-ordering the a and d keys which made hashing on the stringified object unreliable.

The fix is to use the canonical option, like so:

my $json_string = JSON::to_json($object_from_above, {
  convert_blessed => 1,
  canonical => 1 # the fix
});

NB: Big objects will have huge JSON keys, so beware...

Bouchier answered 9/8, 2023 at 0:35 Comment(2)
Perl doesn't order hash keys, and actually tries hard not to. You could do this yourself in the TO_JSON method if it matters to that object. Unrelated to that, you are making a shallow copy in that TO_JSON, which is probably not what you want.Snorter
@briandfoy, I know, usually we don't care about hash order. But if an object is serialized using JSON::to_json (stringified with the " overload) and its keys can change places between invocations, then using the resulting JSON text as a hash key won't work reliably. Since in this case TO_JSON returns a hash, the ordering can't be enforced within TO_JSON. Only the caller (in this case JSON::to_json) can do that, which is what canonical does, since we the JSON text to have its keys ordered.Bouchier

© 2022 - 2024 — McMap. All rights reserved.