Home > Net >  Perl - Hash::Merge loses keys
Perl - Hash::Merge loses keys

Time:09-17

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 :)

CodePudding user response:

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)

  • Related