2

I'm trying to merge two blessed hashes in Perl.

I'm running the following code:

#!usr/bin/perl
use strict;
use warnings;
use Hash::Merge;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;

my $hash1 = bless( {
                 'CalcPorts' => {
                                  'helper_1' => {
                                                  'Scope' => [
                                                               ''
                                                             ],
  
                                                },
                                  'helper_2' => {
                                                  'Scope' => [
                                                               ''
                                                             ],
 
                                                },
                                },
 
               }, 'IB' );
my $hash2 = bless( {
                 'CalcPorts' => {
                                  'helper_2' => {
                                                  'Scope' => [
                                                               'd'
                                                             ],
 
                                                },
                                },
 
               }, 'IB' );

my $merger = Hash::Merge->new('LEFT_PRECEDENT');    
my $hash3 = $merger->merge($hash2, $hash1);

print Dumper($hash3);

The output is this:

$VAR1 = bless( {
                 'CalcPorts' => {
                                  'helper_2' => {
                                                  'Scope' => [
                                                               'd'
                                                             ]
                                                }
                                }
               }, 'IB' );

Even though I would have expected the "helper_1" to be there... Any ideas what am I doing wrong? Thanks for your help :)

urie
  • 361
  • 2
  • 14
  • It seems it doesn't merge blessed hashes at all, try changing the top key in $hash1 to a different string. If you remove [bless](http://p3rl.org/bless), though, it starts to work. – choroba Sep 06 '21 at 08:19
  • But I need it to work with blessing... If I remove the blessing, I need to find a way to return it back when I finish with the merging... and there is blessing all over the values of the hash – urie Sep 06 '21 at 08:24
  • I'd say: For you they are still hashes, but once blessed for Hash::Merge they might be just another unsupported data type. – Sadko Sep 06 '21 at 08:41

1 Answers1

4

Hash::Merge considers that anything whose ref isn't HASH or ARRAY is a scalar, and applies the scalar merging rules on those items (see those lines of the implementation of Hash::Merge). When merging 2 scalars, Hash::Merge either discards one of them, or create an array to store them both. None of this options merges blessed hashes.

To overcome this issue, you can unbless your hashes first (using Data::Structure::Util::unbless for instance), then merge them, then rebless them:

use Data::Structure::Util qw(unbless);
my $class = ref $hash1;
unbless $hash1;
unbless $hash2;
my $hash3 = bless $merger->merge($hash2, $hash1), $class;

If you have blessed hashes within your main hashes, then you can define your own Hash::Merge behavior with the add_behavior_spec method: for the SCALAR-SCALAR case, check if both scalars are blessed references, and, if so, unbless, merge, and rebless:

$merger->add_behavior_spec(
    { 'SCALAR' => {
        'SCALAR' => sub {
            my $self  = &Hash::Merge::_get_obj;
            my ($left, $right) = @_;
            my ($class_left, $class_right) = (ref $left, ref $right);
            if ($class_left && $class_left eq $class_right) {
                unbless $left;
                unbless $right;
                return bless $self->merge($left, $right), $class_left;
            } else {
                return $_[1]; # Or something else
            }
        },
        'ARRAY'  => ...,
        'HASH'   => ...,
    },
    ARRAY => { ... },
    HASH  => { ... }

For conciseness, I've left ... for cases that are not relevant. You can copy-paste those from the source of Hash::Merger (choosing the behavior that you want). Or, maybe easier, you can use get_behavior_spec and get_behavior methods to change the SCALAR-SCALAR case of the current behavior:

my $behavior = $merger->get_behavior_spec($merger->get_behavior);
my $old_behavior_scalar_scalar = $behavior->{SCALAR}{SCALAR};
$behavior->{SCALAR}{SCALAR} = sub {
    my $self  = &Hash::Merge::_get_obj;
    my ($left, $right) = @_;
    my ($class_left, $class_right) = (ref $left, ref $right);
    if ($class_left && $class_left eq $class_right) {
        unbless $left;
        unbless $right;
        return bless $self->merge($left, $right), $class_left;
    } else {
        # Regular scalars, use old behavior
        return $old_behavior_scalar_scalar->($left, $right);
    }
};

Note that this does not handle well cases where you want to merge 2 hashes blessed to 2 different packages. You'll have to implement special cases for those. (it's not clear what a general rule for merging such hashes would be)

Dada
  • 6,313
  • 7
  • 24
  • 43
  • Thanks for your reply! I'm getting a new error (following your code). I posted it [here](https://stackoverflow.com/questions/69114461/perl-deep-recursion-on-subroutine-hashmergemerge). If you could take a look, that would be great. Thanks! – urie Sep 09 '21 at 08:06