This is my scenario, where there are 2 hashes that have been decoded from 2 JSON files.
I have 2 complex hash,
$hash1 = {k1=> { k11 => v1, k12 => v2}, k2 => { k21 => [v1, v2, v3] }}
$hash2 = {k1=> { k11 => v1, k12 => v2}, k2 => { k21 => [v3, v2, v1] }}
I want to compare these 2 hash for equality, and used Compare of Data::Compare and is_deeply of Test::More. Both do not ignore the order of the array.
I want to compare ignoring the order of array values of key 'k21'.
My App populates the array from 'keys %hash' which gives random order.
Tried 'ignore_hash_keys' of Data::Compare, but my hash sometimes can be complex and don't want to ignore.
Key 'k21' can also sometimes have array of hashes.
$hash3 = {k1=> { k11 => v1}, k2 => { k21 => [{v3 => v31}, {v2 => v22}] }}
How do I compare such complex hash by ignoring the array order.
You can use Test::Deep, which provides cmp_deeply
. It's a lot more versatile than Test::More's is_deeply
.
use Test::Deep;
my $hash1 = {
k1 => { k11 => 'v1', k12 => 'v2' }, k2 => { k21 => [ 'v1', 'v2', 'v3' ] } };
my $hash2 = {
k1 => { k11 => 'v1', k12 => 'v2' }, k2 => { k21 => bag( 'v3', 'v2', 'v1' ) } };
cmp_deeply( $hash1, $hash2, );
The trick is the bag()
function, which ignores the order of elements.
This does a bag comparison, that is, it compares two arrays but ignores the order of the elements [...]
Update: From your comment:
How do I bag all array references inside hash dynamically
Some digging in the code of Test::Deep showed that it's possible to overwrite it. I looked at Test::Deep itself first, and found that there is a Test::Deep::Array, which deals with arrays. All of the packages that handle stuff inside of T::D have a descend
method. So that's where we need to hook in.
Sub::Override is great to temporarily override stuff, instead of messing with typeglobs.
Basically all we need to do is replace the call to Test::Deep::arrayelementsonly
in Test::Deep::Array::descend
's final line with a call to bag()
. The rest is simply copied (indentation is mine). For small monkey-patching a copy of the existing code with a slight modification is usually the easiest approach.
use Test::Deep;
use Test::Deep::Array;
use Sub::Override;
my $sub = Sub::Override->new(
'Test::Deep::Array::descend' => sub {
my $self = shift;
my $got = shift;
my $exp = $self->{val};
return 0 unless Test::Deep::descend(
$got, Test::Deep::arraylength( scalar @$exp ) );
return 0 unless $self->test_class($got);
return Test::Deep::descend( $got, Test::Deep::bag(@$exp) );
}
);
my $hash1 = {
k1 => { k11 => 'v1', k12 => 'v2' },
k2 => { k21 => [ 'v1', 'v2', 'v3' ] }
};
my $hash2 = {
k1 => { k11 => 'v1', k12 => 'v2' },
k2 => { k21 => [ 'v3', 'v2', 'v1' ] }
};
cmp_deeply( $hash1, $hash2 );
This will make the test pass.
Make sure to reset the override by undefining $sub
or letting it go out of scope, or you might have some weird surprises if the rest of your test suite also uses Test::Deep.