File Coverage

blib/lib/JSON/Builder.pm
Criterion Covered Total %
statement 170 188 90.4
branch 26 38 68.4
condition 4 11 36.3
subroutine 33 33 100.0
pod 5 9 55.5
total 238 279 85.3


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             JSON::Builder - to build large JSON with temp files when memory limit, and compress optionaly.
4              
5             =head1 SYNOPSIS
6              
7             use JSON::Builder;
8            
9             my $json = JSON::XS->new()->utf8(1)->ascii(1);
10             my ($fh) = tempfile(UNLINK => 1);
11            
12             my $builder = JSON::Builder->new(json => $json, fh => $fh);
13             or
14             my $builder = JSON::Builder::Compress->new(json => $json, fh => $fh); # Compress, Base64
15            
16             my $fv = $builder->val( { a => 'b', c => 'd' } );
17            
18             my $l = $builder->list();
19             $l->add( { 1 => 'a', 2 => 'b' } );
20             $l->add( { 1 => 'c', 2 => 'd' } );
21             my $fl = $l->end();
22            
23             my $o = $builder->obj();
24             $o->add( o1 => ['a', 'b'] );
25             $o->add( o2 => ['c', 'd'] );
26             my $fo = $o->end();
27            
28             my %d = (
29             one => 1,
30             v => $fv,
31             l => $fl,
32             o => $fo,
33             );
34            
35             $builder->encode(\%d);
36            
37             # print for test
38             $fh->flush();
39             $fh->seek(0,0);
40             print <$fh>;
41              
42             =head1 MOTIVATION
43              
44             Task: to create JSON while having the memory limitations.
45            
46             If you have only one large value in JSON, or, large values are created one by one, you can use the streaming generator. Otherwise, you should use such a perl structure where large elements are the filehandle with the json fragments. When a perl structure is transformed into json, it bypasses and large elements are excluded from the files. The result json is written into the file.
47              
48             =head1 DESCRIPTION
49              
50             =head2 JSON::Builder
51              
52             =head3 new
53              
54             The constructor accepts the following arguments:
55              
56             =over
57              
58             =item json
59              
60             JSON object with the encode and allow_nonref methods support, e.g. JSON::XS.
61              
62             =item fh
63              
64             The filehandle of the file where the result should be written into.
65              
66             =item read_in
67              
68             LENGTH of L function. Optional.
69              
70             =back
71              
72             my $builder = JSON::Builder->new(json => $json, fh => $fh);
73              
74             =head3 val
75              
76             It turns the data to JSON, saves JSON into the variable file created and returns the filehandle of this temporary file:
77              
78             my $fv = $builder->val( { a => 'b', c => 'd' } );
79              
80             =head3 list
81              
82             Its returns the object JSON::Builder::List
83              
84             =head3 obj
85              
86             Its returns the object JSON::Builder::Obj
87              
88             =head3 encode
89              
90             Turns the passed data structure into JSON.
91              
92             my %d = (
93             one => 1,
94             v => $fv, # file handler if $builder->val(...)
95             l => $fl, # file handler of JSON::Builder::List
96             o => $fo, # file handler of JSON::Builder::Obj
97             );
98              
99             $builder->encode(\%d)
100              
101             =head2 JSON::Builder::List
102              
103             It is aimed to write the JSON elements list into the temporary file.
104              
105             my $l = $builder->list();
106             $l->add( { 1 => 'a', 2 => 'b' } );
107             $l->add( { 1 => 'c', 2 => 'd' } );
108             my $fl = $l->end();
109              
110             =head3 new
111              
112             Don't use the constructor directly: use the object list method JSON::Builder.
113              
114             =head3 add
115              
116             It adds the element:
117              
118             =head3 end
119              
120             It returns the filehandle of the file with the JSON list.
121              
122             =head2 JSON::Builder::Obj
123              
124             It is for writing the JSON Obj to the temporary file.
125              
126             my $o = $builder->obj();
127             $o->add( o1 => ['a', 'b'] );
128             $o->add( o2 => ['c', 'd'] );
129             my $fo = $o->end();
130              
131             =head3 new
132              
133             Don't use the constructor directly: use the object obj method JSON::Builder.
134              
135             =head3 add
136              
137             Its adds the key-value
138              
139             =head3 end
140              
141             It returns the filehandle of the file with the JSON object.
142              
143             =head2 JSON::Builder::Compress
144              
145             To ensure that the results file includes the JSON packed, use JSON::Builder::Compress instead of JSON::Builder.
146             The packing algorithm: deflate ةع Compress::Zlib.
147             The results of that is encoded with the help of encode_base64url ةع MIME::Base64.
148              
149             =head2 Inheritance
150              
151             If you want to use your own processing algorithm of the JSON portions, you should redeclarate the init, write, write_flush methods for the JSON::Builder object.
152              
153             =head1 AUTHOR
154              
155             Nick Kostyria
156              
157             =head1 COPYRIGHT AND LICENSE
158              
159             Copyright (C) 2013 by Nick Kostyria
160              
161             This library is free software; you can redistribute it and/or modify
162             it under the same terms as Perl itself, either Perl version 5.14.2 or,
163             at your option, any later version of Perl 5 you may have available.
164              
165             =cut
166              
167             package JSON::Builder;
168 1     1   3072538 use strict;
  1         2  
  1         36  
169 1     1   5 use warnings;
  1         1  
  1         39  
170              
171             our $VERSION = '0.03';
172              
173 1     1   4 use Carp;
  1         4  
  1         76  
174 1     1   8 use File::Temp qw(tempfile tempdir);
  1         1  
  1         706  
175              
176             sub new {
177 2     2 1 3009 my $proto = shift;
178 2   33     14 my $class = ref($proto) || $proto;
179              
180 2         7 my $self = { @_ };
181              
182 2         9 $$self{json}->allow_nonref(1);
183              
184 2         7 $$self{dir} = tempdir(CLEANUP => 1);
185              
186 2         577 bless $self, $class;
187              
188 2         8 $self->init();
189              
190 2         5 return $self;
191             }
192              
193              
194             sub init {
195 1     1 0 2 my $self = shift;
196             }
197              
198              
199             sub val {
200 2     2 1 22 my $self = shift;
201 2         3 my ($val) = @_;
202            
203 2         3 my $json_val = eval { $$self{json}->encode($val) };
  2         26  
204 2 50       7 if ($@) {
205 0         0 carp $@;
206 0         0 return;
207             }
208              
209 2         9 my ($fh) = tempfile(DIR => $$self{dir}, UNLINK => 1);
210 2         545 print $fh $json_val;
211 2         83 $fh->flush;
212 2         22 $fh->seek(0,0);
213              
214 2         20 return $fh;
215             }
216              
217              
218             sub list {
219 4     4 1 28 my $self = shift;
220 4         30 JSON::Builder::List->new(%$self);
221             }
222              
223              
224             sub obj {
225 4     4 1 16 my $self = shift;
226 4         21 JSON::Builder::Obj->new(%$self);
227             }
228              
229              
230             sub encode {
231 2     2 1 27 my $self = shift;
232 2         2 my ($d) = @_;
233              
234 2         3 my $json = $$self{json};
235 2         4 my $fh = $$self{fh};
236              
237 2         7 $self->kv($d);
238 2         4 $self->write_flush();
239              
240 2         45 $fh->flush;
241 2         7 $fh->seek(0,0);
242             }
243              
244              
245             sub kv {
246 14     14 0 11 my $self = shift;
247 14         14 my ($d) = @_;
248              
249 14 50       45 if (ref $d eq "ARRAY") {
    100          
    100          
    50          
250 0         0 $self->write("[");
251 0         0 my $i = @$d;
252 0         0 foreach (@$d) {
253 0         0 $self->kv($_);
254 0 0       0 $self->write(",") if --$i;
255             }
256 0         0 $self->write("]");
257             } elsif (ref $d eq "HASH") {
258 2         4 my $json = $$self{json};
259 2         7 $self->write("{");
260 2         6 my $i = keys %$d;
261 2         6 foreach (keys %$d) {
262 12         26 $self->write($json->encode($_), ':');
263 12         22 $self->kv($$d{$_});
264 12 100       25 $self->write(",") if --$i;
265             }
266 2         4 $self->write("}");
267             } elsif (ref $d eq "GLOB") {
268 10   50     76 while (read($d, my $buf, $$self{read_in} || 57000)) {
269 10         14 $self->write($buf);
270             }
271             } elsif (not ref $d) {
272 2         4 my $json = $$self{json};
273 2         7 $self->write($json->encode($d));
274             }
275             }
276              
277              
278             sub write {
279 19     19 0 12 my $self = shift;
280 19         15 print { $$self{fh} } @_;
  19         48  
281             }
282              
283             sub write_flush {
284 1     1 0 2 my $self = shift;
285             }
286              
287              
288             package JSON::Builder::List;
289 1     1   6 use strict;
  1         1  
  1         27  
290 1     1   3 use warnings;
  1         1  
  1         24  
291              
292 1     1   4 use File::Temp qw(tempfile);
  1         1  
  1         228  
293              
294             sub new {
295 4     4   5 my $proto = shift;
296 4   33     18 my $class = ref($proto) || $proto;
297              
298 4         32 my $self = { @_, first => 1 };
299              
300 4         16 $$self{fh} = tempfile(DIR => $$self{dir}, UNLINK => 1);
301              
302 4         1582 bless $self, $class;
303 4         14 return $self;
304             }
305              
306              
307             sub add {
308 4     4   28 my $self = shift;
309 4         6 my ($val) = @_;
310              
311 4         2 my $json_val = eval { $$self{json}->encode($val) };
  4         23  
312 4 50       12 if ($@) {
313 0         0 carp $@;
314 0         0 return;
315             }
316              
317 4 100       9 if ($$self{first}) {
318 2         4 $$self{first} = 0;
319 2         2 print { $$self{fh} } "[", $json_val;
  2         23  
320             } else {
321 2         2 print { $$self{fh} } ",", $json_val;
  2         5  
322             }
323             }
324              
325              
326             sub end {
327 4     4   10 my $self = shift;
328 4         4 my $fh = $$self{fh};
329              
330 4 100       8 if ($$self{first}) {
331 2         4 $$self{first} = 0;
332 2         25 print $fh "[";
333             }
334 4         5 print $fh "]";
335              
336 4         85 $fh->flush;
337 4         14 $fh->seek(0,0);
338 4         26 return $fh;
339             }
340              
341              
342              
343             package JSON::Builder::Obj;
344 1     1   4 use strict;
  1         2  
  1         28  
345 1     1   3 use warnings;
  1         1  
  1         20  
346              
347 1     1   4 use File::Temp qw(tempfile);
  1         1  
  1         257  
348              
349             sub new {
350 4     4   5 my $proto = shift;
351 4   33     21 my $class = ref($proto) || $proto;
352              
353 4         12 my $self = { @_, first => 1 };
354              
355 4         12 $$self{fh} = tempfile(DIR => $$self{dir}, UNLINK => 1);
356              
357 4         1440 bless $self, $class;
358 4         12 return $self;
359             }
360              
361              
362             sub add {
363 4     4   33 my $self = shift;
364 4         6 my ($key, $val) = @_;
365              
366 4         4 my $json_key = eval { $$self{json}->encode($key) };
  4         17  
367 4 50       10 if ($@) {
368 0         0 carp $@;
369 0         0 return;
370             }
371              
372 4         3 my $json_val = eval { $$self{json}->encode($val) };
  4         11  
373 4 50       15 if ($@) {
374 0         0 carp $@;
375 0         0 return;
376             }
377              
378 4 100       7 if ($$self{first}) {
379 2         3 $$self{first} = 0;
380 2         2 print { $$self{fh} } "{", $json_key, ":", $json_val;
  2         23  
381             } else {
382 2         3 print { $$self{fh} } ",", $json_key, ":", $json_val;
  2         7  
383             }
384             }
385              
386              
387             sub end {
388 4     4   10 my $self = shift;
389 4         6 my $fh = $$self{fh};
390              
391 4 100       9 if ($$self{first}) {
392 2         4 $$self{first} = 0;
393 2         21 print $fh "{";
394             }
395 4         5 print $fh "}";
396              
397 4         163 $fh->flush;
398 4         14 $fh->seek(0,0);
399 4         30 return $fh;
400             }
401              
402              
403              
404             package JSON::Builder::Compress; # Compress, Base64
405 1     1   5 use strict;
  1         1  
  1         26  
406 1     1   3 use warnings;
  1         1  
  1         24  
407 1     1   6 use base qw(JSON::Builder);
  1         1  
  1         102  
408              
409 1     1   9 use Compress::Zlib;
  1         1  
  1         302  
410 1     1   8 use MIME::Base64 qw(encode_base64url);
  1         1  
  1         301  
411              
412             sub init {
413 1     1   2 my $self = shift;
414 1         6 $$self{x} = deflateInit();
415 1         455 $$self{write_buf} = "";
416             }
417              
418              
419             sub write {
420 19     19   18 my $self = shift;
421              
422 19         24 my $buf = join "", @_;
423              
424 19         32 my ($output, $status) = $$self{x}->deflate($buf);
425 19 50       135 $status == Z_OK or die "deflation failed\n";
426              
427 19 100       78 if ($output) {
428 1         4 my $write_buf = join "", $$self{write_buf}, $output;
429 1         4 my $l = int(length($write_buf)/ 57) * 57;
430 1 50       3 if ($l) {
431 0         0 my $buf_head = substr $write_buf, 0, $l;
432 0         0 $$self{write_buf} = substr $write_buf, $l;
433 0         0 print { $$self{fh} } encode_base64url($buf_head, "");
  0         0  
434             } else {
435 1         2 $$self{write_buf} = $write_buf;
436             }
437             }
438             };
439              
440              
441             sub write_flush {
442 1     1   2 my $self = shift;
443              
444 1         7 my ($output, $status) = $$self{x}->flush();
445 1 50       71 $status == Z_OK or die "deflation failed\n";
446              
447 1 50       6 if ($output) {
448 1         3 $$self{write_buf} .= $output;
449             }
450              
451 1         1 print { $$self{fh} } encode_base64url($$self{write_buf}, "");
  1         9  
452              
453 1         24 $$self{write_buf} = "";
454             }
455              
456              
457             1;