File Coverage

blib/lib/Data/Difference.pm
Criterion Covered Total %
statement 48 51 94.1
branch 42 48 87.5
condition n/a
subroutine 7 7 100.0
pod 0 1 0.0
total 97 107 90.6


line stmt bran cond sub pod time code
1             package Data::Difference 0.113002;
2             # ABSTRACT: Compare simple hierarchical data
3              
4 1     1   101083 use strict;
  1         1  
  1         30  
5 1     1   3 use warnings;
  1         1  
  1         52  
6 1     1   5 use base 'Exporter';
  1         1  
  1         122  
7              
8 1     1   4 use Scalar::Util ();
  1         1  
  1         728  
9              
10             our @EXPORT_OK = qw(data_diff);
11              
12             sub data_diff {
13 17     17 0 396370 my ($left, $right) = @_;
14              
15 17 50       62 if (ref($left)) {
    100          
    100          
    100          
16 14 50       165 if (my $sub = __PACKAGE__->can("_diff_" . ref($left))) {
17 14         50 return $sub->($left, $right, {});
18             }
19             else {
20 0         0 return {path => [], a => $left, b => $right};
21             }
22             }
23             elsif (defined $left ? defined $right ? $left ne $right : 1 : defined $right) {
24 2         20 return {path => [], a => $left, b => $right};
25             }
26              
27 1         11 return;
28             }
29              
30             sub _diff_HASH {
31 20     20   57 my ($left, $right, $seen, @path) = @_;
32              
33 20 50       60 return {path => \@path, a => $left, b => $right} unless ref($left) eq ref($right);
34              
35 20         51 my $seen_key = Scalar::Util::refaddr($left) . ':' . Scalar::Util::refaddr($right);
36 20 100       61 return if $seen->{$seen_key};
37 15         44 local $seen->{$seen_key} = 1;
38              
39 15         58 my @diff;
40             my %k;
41 15         96 @k{keys %$left, keys %$right} = ();
42 15         56 foreach my $k (sort keys %k) {
43 24 100       125 if (!exists $left->{$k}) {
    100          
    100          
    100          
    100          
    100          
44 1         5 push @diff, {path => [@path, $k], b => $right->{$k}};
45             }
46             elsif (!exists $right->{$k}) {
47 1         7 push @diff, {path => [@path, $k], a => $left->{$k}};
48             }
49             elsif (ref($left->{$k})) {
50 11 50       59 if (my $sub = __PACKAGE__->can("_diff_" . ref($left->{$k}))) {
51 11         34 push @diff, $sub->($left->{$k}, $right->{$k}, $seen, @path, $k);
52             }
53             else {
54 0         0 push @diff, {path => [@path, $k], a => $left->{$k}, b => $right->{$k}};
55             }
56             }
57             elsif (defined $left->{$k} ? defined $right->{$k} ? $right->{$k} ne $left->{$k} : 1 : defined $right->{$k}) {
58 6         54 push @diff, {path => [@path, $k], a => $left->{$k}, b => $right->{$k}};
59             }
60             }
61              
62 15         117 return @diff;
63             }
64              
65             sub _diff_ARRAY {
66 7     7   21 my ($left, $right, $seen, @path) = @_;
67 7 50       28 return {path => \@path, a => $left, b => $right} unless ref($left) eq ref($right);
68              
69 7         24 my $seen_key = Scalar::Util::refaddr($left) . ':' . Scalar::Util::refaddr($right);
70 7 100       22 return if $seen->{$seen_key};
71 6         19 local $seen->{$seen_key} = 1;
72              
73 6         13 my @diff;
74 6 100       18 my $n = $#$left > $#$right ? $#$left : $#$right;
75              
76 6         18 foreach my $i (0 .. $n) {
77 10 100       55 if ($i > $#$left) {
    100          
    100          
    100          
    100          
    100          
78 1         7 push @diff, {path => [@path, $i], b => $right->[$i]};
79             }
80             elsif ($i > $#$right) {
81 1         6 push @diff, {path => [@path, $i], a => $left->[$i]};
82             }
83             elsif (ref($left->[$i])) {
84 2 50       16 if (my $sub = __PACKAGE__->can("_diff_" . ref($left->[$i]))) {
85 2         18 push @diff, $sub->($left->[$i], $right->[$i], $seen, @path, $i);
86             }
87             else {
88 0         0 push @diff, {path => [@path, $i], a => $left->[$i], b => $right->[$i]};
89             }
90             }
91             elsif (defined $left->[$i] ? defined $right->[$i] ? $right->[$i] ne $left->[$i] : 1 : defined $right->[$i]) {
92 2         13 push @diff, {path => [@path, $i], a => $left->[$i], b => $right->[$i]};
93             }
94             }
95              
96 6         49 return @diff;
97             }
98              
99             1;
100              
101             =pod
102              
103             =encoding UTF-8
104              
105             =head1 NAME
106              
107             Data::Difference - Compare simple hierarchical data
108              
109             =head1 VERSION
110              
111             version 0.113002
112              
113             =head1 DESCRIPTION
114              
115             C will compare simple data structures returning a list of details about what was
116             added, removed or changed. It will currently handle SCALARs, HASH references and ARRAY references.
117              
118             Each change is returned as a hash with the following element.
119              
120             =over
121              
122             =item path
123              
124             path will be an ARRAY reference containing the hierarchical path to the value, each element in the array
125             will be either the key of a hash or the index on an array
126              
127             =item a
128              
129             If it exists it will contain the value from the first argument passed to C. If it
130             does not exist then this element did not exist in the first argument.
131              
132             =item b
133              
134             If it exists it will contain the value from the second argument passed to C. If it
135             does not exist then this element did not exist in the second argument.
136              
137             =back
138              
139             =head1 PERL VERSION
140              
141             This library should run on perls released even a long time ago. It should
142             work on any version of perl released in the last five years.
143              
144             Although it may work on older versions of perl, no guarantee is made that the
145             minimum required version will not be increased. The version may be increased
146             for any reason, and there is no promise that patches will be accepted to
147             lower the minimum required perl.
148              
149             =head1 SYNOPSYS
150              
151             use Data::Difference qw(data_diff);
152             use Data::Dumper;
153              
154             my %from = (Q => 1, W => 2, E => 3, X => [1,2,3], Y=> [5,6]);
155             my %to = (W => 4, E => 3, R => 5, => X => [1,2], Y => [5,7,9]);
156             my @diff = data_diff(\%from, \%to);
157              
158             @diff = (
159             # value $a->{Q} was deleted
160             { 'a' => 1, 'path' => ['Q'] },
161              
162             # value $b->{R} was added
163             { 'b' => 5, 'path' => ['R'] },
164              
165             # value $a->{W} changed
166             { 'a' => 2, 'b' => 4, 'path' => ['W'] },
167              
168             # value $a->{X}[2] was deleted
169             { 'a' => 3, 'path' => ['X', 2] },
170              
171             # value $a->{Y}[1] was changed
172             { 'a' => 6, 'b' => 7, 'path' => ['Y', 1] },
173              
174             # value $b->{Y}[2] was added
175             { 'b' => 9, 'path' => ['Y', 2] },
176             );
177              
178             =head1 AUTHOR
179              
180             Graham Barr C<< >>
181              
182             =head1 COPYRIGHT
183              
184             Copyright (c) 2011 Graham Barr. All rights reserved.
185             This program is free software; you can redistribute it and/or modify it
186             under the same terms as Perl itself.
187              
188             =head1 AUTHOR
189              
190             Graham Barr
191              
192             =head1 CONTRIBUTORS
193              
194             =for stopwords Graham Barr Ricardo Signes
195              
196             =over 4
197              
198             =item *
199              
200             Graham Barr
201              
202             =item *
203              
204             Ricardo Signes
205              
206             =back
207              
208             =head1 COPYRIGHT AND LICENSE
209              
210             This software is copyright (c) 2026 by Graham Barr.
211              
212             This is free software; you can redistribute it and/or modify it under
213             the same terms as the Perl 5 programming language system itself.
214              
215             =cut
216              
217             __END__