File Coverage

blib/lib/JSON/Pointer.pm
Criterion Covered Total %
statement 164 172 95.3
branch 76 98 77.5
condition 18 24 75.0
subroutine 23 24 95.8
pod 10 10 100.0
total 291 328 88.7


line stmt bran cond sub pod time code
1             package JSON::Pointer;
2              
3 11     11   482608 use 5.008_001;
  11         43  
  11         430  
4 11     11   62 use strict;
  11         19  
  11         342  
5 11     11   57 use warnings;
  11         25  
  11         313  
6              
7 11     11   51 use B;
  11         19  
  11         651  
8 11     11   56 use Carp qw(croak);
  11         21  
  11         612  
9 11     11   8106 use Clone qw(clone);
  11         37348  
  11         780  
10 11     11   3620 use JSON qw(encode_json decode_json);
  11         53502  
  11         78  
11 11     11   8608 use JSON::Pointer::Context;
  11         31  
  11         374  
12 11     11   6204 use JSON::Pointer::Exception qw(:all);
  11         30  
  11         1650  
13 11     11   5934 use JSON::Pointer::Syntax qw(is_array_numeric_index);
  11         32  
  11         755  
14 11     11   10025 use URI::Escape qw(uri_unescape);
  11         16805  
  11         24416  
15              
16             our $VERSION = '0.06';
17              
18             sub traverse {
19 141     141 1 36544 my ($class, $document, $pointer, $opts) = @_;
20 141 100       794 $opts = +{
21             strict => 1,
22             inclusive => 0,
23 141         183 %{ $opts || +{} }
24             };
25 141         517 $pointer = uri_unescape($pointer);
26              
27 141         1283 my @tokens = JSON::Pointer::Syntax->tokenize($pointer);
28 141         986 my $context = JSON::Pointer::Context->new(+{
29             pointer => $pointer,
30             tokens => \@tokens,
31             target => $document,
32             parent => $document,
33             });
34              
35 141         286 foreach my $token (@tokens) {
36 182         496 $context->begin($token);
37              
38 182         458 my $parent = $context->parent;
39 182         813 my $type = ref $parent;
40              
41 182 100       413 if ($type eq "HASH") {
    50          
42 129 100       294 unless (exists $parent->{$token}) {
43 24         81 return _throw_or_return(ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE, $context, $opts->{strict});
44             }
45              
46 105         298 $context->next($parent->{$token});
47 105         221 next;
48             }
49             elsif ($type eq "ARRAY") {
50 53 100       135 if ($token eq '-') {
51 1         2 $token = $#{$parent} + 1;
  1         3  
52             }
53              
54 53         74 my $max_index = $#{$parent};
  53         101  
55 53 100       141 $max_index++ if $opts->{inclusive};
56              
57 53 100 100     172 if (is_array_numeric_index($token) && $token <= $max_index) {
58 41         146 $context->next($parent->[$token]);
59 41         123 next;
60             }
61             else {
62 12         45 return _throw_or_return(ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE, $context, $opts->{strict});
63             }
64             }
65             else {
66 0         0 return _throw_or_return(ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE, $context, $opts->{strict});
67             }
68             }
69              
70 105         327 $context->result(1);
71 105         691 return $context;
72             }
73              
74             sub get {
75 33     33 1 28717 my ($class, $document, $pointer, $strict) = @_;
76 33 100       91 $strict = 0 unless defined $strict;
77              
78 33         29 my $context;
79 33         80 eval {
80 33         113 $context = $class->traverse($document, $pointer, +{ strict => $strict });
81             };
82 33 100       307 if (my $e = $@) {
83 3         24 croak $e;
84             }
85              
86 30 100       64 return $context->result ? $context->target : undef;
87             }
88              
89             sub contains {
90 9     9 1 3236 my ($class, $document, $pointer) = @_;
91 9         30 my $context = $class->traverse($document, $pointer, +{ strict => 0 });
92 9         27 return $context->result;
93             }
94              
95             sub add {
96 34     34 1 44224 my ($class, $document, $pointer, $value) = @_;
97              
98 34         371 my $patched_document = clone($document);
99              
100 34         168 my $context = $class->traverse($patched_document, $pointer, +{ strict => 0, inclusive => 1 });
101 34         137 my $parent = $context->parent;
102 34         157 my $type = ref $parent;
103              
104 34 100       106 if ($type eq "HASH") {
    50          
105 21 100 100     56 if (!$context->result && @{$context->processed_tokens} < @{$context->tokens} - 1) {
  19         134  
  19         121  
106             ### Parent isn't object
107 2         17 JSON::Pointer::Exception->throw(
108             code => ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE,
109             context => $context,
110             );
111             }
112              
113 19 100       161 if (defined $context->last_token) {
114 18         120 $parent->{$context->last_token} = $value;
115             }
116             else {
117             ### pointer is empty string (whole document)
118 1         7 $patched_document = $value;
119             }
120              
121 19         199 return $patched_document;
122             }
123             elsif ($type eq "ARRAY") {
124 13 100       35 unless ($context->result) {
125 3         63 JSON::Pointer::Exception->throw(
126             code => ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE,
127             context => $context,
128             );
129             }
130              
131 10 50       66 if (defined $context->last_token) {
132 10         50 my $parent_array_length = $#{$parent} + 1;
  10         64  
133 10 100       28 my $target_index = ($context->last_token eq "-") ?
134             $parent_array_length : $context->last_token;
135              
136 10         104 splice(@$parent, $target_index, 0, $value);
137             }
138             else {
139 0         0 $patched_document = $value;
140             }
141              
142 10         58 return $patched_document;
143             }
144             else {
145 0 0       0 unless ($context->result) {
146 0         0 JSON::Pointer::Exception->throw(
147             code => ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE,
148             context => $context,
149             );
150             }
151              
152 0         0 return $value;
153             }
154             }
155              
156             sub remove {
157 14     14 1 23738 my ($class, $document, $pointer) = @_;
158              
159 14         187 my $patched_document = clone($document);
160              
161 14         54 my $context = $class->traverse($patched_document, $pointer);
162 13         37 my $parent = $context->parent;
163 13         64 my $type = ref $parent;
164              
165 13 100       44 if ($type eq "HASH") {
    100          
166 8         25 my $target_member = $context->last_token;
167 8 100       66 if (defined $target_member) {
168 7         17 my $removed = delete $parent->{$target_member};
169 7 50       74 return wantarray ? ($patched_document, $removed) : $patched_document;
170             }
171             else {
172             ### pointer is empty string (whole document)
173 1 50       9 return wantarray ? (undef, $patched_document) : undef;
174             }
175             }
176             elsif ($type eq "ARRAY") {
177 4         14 my $target_index = $context->last_token;
178 4 100       29 if (defined $target_index) {
179 3         8 my $parent_array_length = $#{$parent} + 1;
  3         8  
180 3 50       11 $target_index = $parent_array_length if ($target_index eq "-");
181 3         10 my $removed = splice(@$parent, $target_index, 1);
182 3 50       27 return wantarray ? ($patched_document, $removed) : $patched_document;
183             }
184             else {
185             ### pointer is empty string (whole document)
186 1 50       8 return wantarray ? (undef, $patched_document) : undef;
187             }
188             }
189             else {
190 1 50       5 unless ($context->result) {
191 0         0 JSON::Pointer::Exception->throw(
192             code => ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE,
193             context => $context,
194             );
195             }
196              
197 1 50       18 return wantarray ? (undef, $patched_document) : undef;
198             }
199             }
200              
201             sub replace {
202 12     12 1 41536 my ($class, $document, $pointer, $value) = @_;
203              
204 12         182 my $patched_document = clone($document);
205 12         56 my $context = $class->traverse($patched_document, $pointer);
206 12         35 my $parent = $context->parent;
207 12         60 my $type = ref $parent;
208              
209 12 100       26 if ($type eq "HASH") {
210 4         13 my $target_member = $context->last_token;
211 4 100       25 if (defined $target_member) {
212 3         9 my $replaced = $parent->{$context->last_token};
213 3         19 $parent->{$context->last_token} = $value;
214 3 50       52 return wantarray ? ($patched_document, $replaced) : $patched_document;
215             }
216             else {
217             ### pointer is empty string (whole document)
218 1 50       10 return wantarray ? ($value, $patched_document) : $value;
219             }
220             }
221             else {
222 8         31 my $target_index = $context->last_token;
223 8 100       48 if (defined $target_index) {
224 6         9 my $parent_array_length = $#{$parent} + 1;
  6         12  
225 6 50       18 $target_index = $parent_array_length if ($target_index eq "-");
226 6         15 my $replaced = $parent->[$target_index];
227 6         11 $parent->[$target_index] = $value;
228 6 50       51 return wantarray ? ($patched_document, $replaced) : $patched_document;
229             }
230             else {
231             ### pointer is empty string (whole document)
232 2 50       18 return wantarray ? ($value, $patched_document) : $value;
233             }
234             }
235             }
236              
237             sub set {
238 0     0 1 0 shift->replace(@_);
239             }
240              
241             sub copy {
242 1     1 1 1463 my ($class, $document, $from_pointer, $to_pointer) = @_;
243 1         5 my $context = $class->traverse($document, $from_pointer);
244 1         3 return $class->add($document, $to_pointer, $context->target);
245             }
246              
247             sub move {
248 5     5 1 12459 my ($class, $document, $from_pointer, $to_pointer) = @_;
249 5         17 my ($patched_document, $removed) = $class->remove($document, $from_pointer);
250 5         14 $class->add($patched_document, $to_pointer, $removed);
251             }
252              
253             sub test {
254 23     23 1 35520 my ($class, $document, $pointer, $value) = @_;
255              
256 23         94 my $context = $class->traverse($document, $pointer, +{ strict => 0 });
257              
258 23 100       58 return 0 unless $context->result;
259              
260 22         121 my $target = $context->target;
261 22         76 my $target_type = ref $target;
262              
263 22 100 100     97 if ($target_type eq "HASH" || $target_type eq "ARRAY") {
    100          
264 4 100       40 return encode_json($target) eq encode_json($value) ? 1 : 0;
265             }
266             elsif (defined $target) {
267 17 50 100     62 if (JSON::is_bool($target)) {
    100 66        
    100          
268 0 0 0     0 return JSON::is_bool($value) && $target == $value ? 1 : 0;
269             }
270             elsif (_is_iv_or_nv($target) && _is_iv_or_nv($value)) {
271 12 50       75 return $target == $value ? 1 : 0;
272             }
273             elsif (_is_pv($target) && _is_pv($value)) {
274 4 100       27 return $target eq $value ? 1 : 0;
275             }
276             else {
277 1         4 return 0;
278             }
279             }
280             else {
281             ### null
282 1 50       7 return !defined $value ? 1 : 0;
283             }
284             }
285              
286             sub _throw_or_return {
287 36     36   61 my ($code, $context, $strict) = @_;
288              
289 36 100       77 if ($strict) {
290 4         24 JSON::Pointer::Exception->throw(
291             code => $code,
292             context => $context,
293             );
294             }
295             else {
296 32         93 $context->last_error($code);
297 32         243 return $context;
298             }
299             }
300              
301             sub _is_iv_or_nv {
302 30     30   112 my $value = shift;
303 30         125 my $flags = B::svref_2object(\$value)->FLAGS;
304 30   66     182 return ( ($flags & ( B::SVp_IOK | B::SVp_NOK )) and !($flags & B::SVp_POK) );
305             }
306              
307             sub _is_pv {
308 9     9   11 my $value = shift;
309 9         27 my $flags = B::svref_2object(\$value)->FLAGS;
310 9   66     50 return ( !($flags & ( B::SVp_IOK | B::SVp_NOK )) and ($flags & B::SVp_POK) );
311             }
312              
313             1;
314              
315             __END__