File Coverage

blib/lib/Data/Delete.pm
Criterion Covered Total %
statement 51 62 82.2
branch 26 40 65.0
condition 6 12 50.0
subroutine 8 9 88.8
pod 1 1 100.0
total 92 124 74.1


line stmt bran cond sub pod time code
1 2     2   213946 use strict;
  2         5  
  2         71  
2 2     2   14 use warnings;
  2         3  
  2         195  
3              
4             package Data::Delete;
5             $Data::Delete::VERSION = '0.07';
6 2     2   1540 use Moo;
  2         22006  
  2         39  
7 2     2   4870 use MooX::Types::MooseLike::Base qw/HashRef Bool/;
  2         20465  
  2         1679  
8              
9             =head1 NAME
10              
11             Data::Delete - Delete keys with undefined or empty string values in a deep data structure
12              
13             =head1 SYNOPSIS
14              
15             use Data::Delete;
16             my $dd = Data::Delete->new;
17             my $deep_data_structure = {
18             id => 4,
19             last_modified => undef,
20             sections => [
21             {
22             content => 'h1. Ice Cream',
23             class => 'textile'
24             },
25             {
26             content => '# Pie',
27             class => ''
28             },
29             ],
30             };
31             use Data::Dumper;
32             print Dumper $dd->delete($deep_data_structure);
33              
34             # results in:
35              
36             {
37             id => "4",
38             sections => [
39             {
40             content => 'h1. Ice Cream',
41             class => 'textile'
42             },
43             {
44             content => "# Pie"
45             }
46             ]
47             }
48              
49             =head1 DESCRIPTION
50              
51             A module for when you want to remove HashRef keys when the value is undefined
52             or an empty string.
53              
54             =cut
55              
56             has 'references_seen' => (
57             is => 'rw',
58             isa => HashRef,
59             );
60              
61             =head2 debug_delete
62              
63             Turn on/off debugging
64              
65             =cut
66              
67             has 'debug_delete' => (
68             is => 'ro',
69             isa => Bool,
70             );
71              
72             =head2 will_delete_empty_string
73              
74             Choose to remove empty string hash values - default true
75              
76             =cut
77              
78             has 'will_delete_empty_string' => (
79             is => 'lazy',
80             isa => Bool,
81 1     1   17 builder => sub { 1 },
82             );
83              
84             =head2 will_delete_empty_ref
85              
86             Choose to remove empty array, hash and scalar ref values - default false
87              
88             =cut
89              
90              
91             has 'will_delete_empty_ref' => (
92             is => 'lazy',
93             isa => Bool,
94 0     0   0 builder => sub { 0 },
95             );
96              
97              
98             =head1 METHODS
99              
100             =head2 delete
101              
102             Signature: (HashRef|ArrayRef)
103             Returns: The data structure with undefined hash values, and optionally,
104             empty string hash values removed
105              
106             =cut
107              
108             sub delete {
109 4     4 1 261382 my ( $self, $data ) = @_;
110 4 100       14 if ( ref($data) eq 'HASH' ) {
    50          
111 2         6 return $self->_delete_hash($data);
112             }
113             elsif ( ref($data) eq 'ARRAY' ) {
114 2         5 return $self->_delete_array($data);
115             }
116             else {
117 0         0 die "You must pass the delete method either a HashRef or an ArrayRef";
118             }
119             }
120              
121             sub _delete_hash {
122 8     8   13 my ( $self, $hashref ) = @_;
123              
124             # Work on a copy
125 8         8 my %hashref = %{$hashref};
  8         23  
126 8         10 $hashref = \%hashref;
127              
128 8         7 foreach my $key ( keys %{$hashref} ) {
  8         15  
129 19         59 my $value = $hashref->{$key};
130 19         22 my $ref_value = ref($value);
131 19         222 my $references_seen = $self->references_seen;
132              
133             # Skip if we've seen this ref before
134 19 50 66     81 if ( $ref_value and $references_seen->{$value} ) {
135 0 0       0 warn "Seen referenced value: $value before" if $self->debug_delete;
136 0         0 next;
137             }
138              
139             # If we have a reference value then note it to avoid deep recursion
140             # with circular references.
141 19 100       26 if ($ref_value) {
142 4         6 $references_seen->{$value} = 1;
143 4         39 $self->references_seen($references_seen);
144             }
145 19 100       151 if ( not $ref_value ) {
    100          
    100          
    50          
146              
147             # Delete a key when the value is not defined
148 15 100       26 if ( not defined $value ) {
    100          
149 6         32 delete $hashref->{$key};
150             }
151              
152             # Optionally delete an empty string value
153             elsif ( length($value) == 0 ) {
154 3 100       32 delete $hashref->{$key} if $self->will_delete_empty_string;
155             }
156              
157             # Make no change
158             else { }
159             }
160              
161             # Look inside HashRefs
162             elsif ( $ref_value eq 'HASH' ) {
163 1 50 33     23 if (!%$value and $self->will_delete_empty_ref) {
164 1         12 delete $hashref->{$key};
165             }
166             else {
167             # Recurse when a value is a HashRef
168 0         0 $hashref->{$key} = $self->_delete_hash($value);
169             }
170             }
171              
172             # Look inside ArrayRefs for HashRefs
173             elsif ( $ref_value eq 'ARRAY' ) {
174 2 100 66     15 if (!@$value and $self->will_delete_empty_ref) {
175 1         6 delete $hashref->{$key};
176             }
177             else {
178 1         5 $hashref->{$key} = $self->_delete_array($value);
179             }
180             }
181              
182             elsif ( $ref_value eq 'SCALAR' ) {
183 1 50 33     14 if ( length($$value) == 0 and $self->will_delete_empty_ref ) {
184 1         9 delete $hashref->{$key};
185             }
186             }
187              
188             # Leave alone
189             else { }
190             }
191 8         25 return $hashref;
192             }
193              
194             sub _delete_array {
195 3     3   6 my ( $self, $arrayref ) = @_;
196              
197 3         56 my $references_seen = $self->references_seen;
198             $arrayref = [
199             map {
200 12 100       18 if ( ref($_) ) {
201 6 50       10 if ( ref($_) eq 'HASH' ) {
    0          
202 6         10 $self->_delete_hash($_);
203             }
204             elsif ( ref($_) eq 'ARRAY' ) {
205              
206             # Skip if we've seen this ref before
207 0 0       0 if ( $references_seen->{$_} ) {
208 0 0       0 warn "Seen referenced value: $_ before"
209             if $self->debug_delete;
210 0         0 $_;
211             }
212             else {
213 0         0 $references_seen->{$_} = 1;
214 0         0 $self->references_seen($references_seen);
215 0         0 $self->_delete_array($_);
216             }
217             }
218             }
219             else {
220 6         12 $_;
221             }
222 3         15 } @{$arrayref}
  3         6  
223             ];
224 3         8 return $arrayref;
225             }
226              
227             1;
228              
229             =head1 AUTHORS
230              
231             Mateu X Hunter C
232              
233             =head1 COPYRIGHT
234              
235             Copyright 2024, Mateu X Hunter
236              
237             =head1 LICENSE
238              
239             You may distribute this code under the same terms as Perl itself.
240              
241             =cut