File Coverage

blib/lib/JSON/Builder.pm
Criterion Covered Total %
statement 164 182 90.1
branch 22 34 64.7
condition 4 11 36.3
subroutine 33 33 100.0
pod 5 9 55.5
total 228 269 84.7


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   436088 use strict;
  1         3  
  1         50  
169 1     1   5 use warnings;
  1         2  
  1         54  
170              
171             our $VERSION = '0.02';
172              
173 1     1   6 use Carp;
  1         6  
  1         102  
174 1     1   18 use File::Temp qw(tempfile tempdir);
  1         2  
  1         904  
175              
176             sub new {
177 2     2 1 3411 my $proto = shift;
178 2   33     17 my $class = ref($proto) || $proto;
179              
180 2         10 my $self = { @_ };
181              
182 2         9 $$self{json}->allow_nonref(1);
183              
184 2         10 $$self{dir} = tempdir(CLEANUP => 1);
185              
186 2         935 bless $self, $class;
187              
188 2         11 $self->init();
189              
190 2         9 return $self;
191             }
192              
193              
194             sub init {
195 1     1 0 3 my $self = shift;
196             }
197              
198              
199             sub val {
200 2     2 1 30 my $self = shift;
201 2         5 my ($val) = @_;
202            
203 2         5 my $json_val = eval { $$self{json}->encode($val) };
  2         41  
204 2 50       10 if ($@) {
205 0         0 carp $@;
206 0         0 return;
207             }
208              
209 2         23 my ($fh) = tempfile(DIR => $$self{dir}, UNLINK => 1);
210 2         958 print $fh $json_val;
211 2         108 $fh->flush;
212 2         23 $fh->seek(0,0);
213              
214 2         28 return $fh;
215             }
216              
217              
218             sub list {
219 2     2 1 19 my $self = shift;
220 2         21 JSON::Builder::List->new(%$self);
221             }
222              
223              
224             sub obj {
225 2     2 1 54 my $self = shift;
226 2         20 JSON::Builder::Obj->new(%$self);
227             }
228              
229              
230             sub encode {
231 2     2 1 25 my $self = shift;
232 2         4 my ($d) = @_;
233              
234 2         3 my $json = $$self{json};
235 2         5 my $fh = $$self{fh};
236              
237 2         9 $self->kv($d);
238 2         6 $self->write_flush();
239              
240 2         68 $fh->flush;
241 2         10 $fh->seek(0,0);
242             }
243              
244              
245             sub kv {
246 10     10 0 11 my $self = shift;
247 10         12 my ($d) = @_;
248              
249 10 50       51 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         8 $self->write("{");
260 2         6 my $i = keys %$d;
261 2         8 foreach (keys %$d) {
262 8         30 $self->write($json->encode($_), ':');
263 8         26 $self->kv($$d{$_});
264 8 100       33 $self->write(",") if --$i;
265             }
266 2         7 $self->write("}");
267             } elsif (ref $d eq "GLOB") {
268 6   50     106 while (read($d, my $buf, $$self{read_in} || 57000)) {
269 6         15 $self->write($buf);
270             }
271             } elsif (not ref $d) {
272 2         4 my $json = $$self{json};
273 2         9 $self->write($json->encode($d));
274             }
275             }
276              
277              
278             sub write {
279 13     13 0 17 my $self = shift;
280 13         13 print { $$self{fh} } @_;
  13         59  
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   8 use strict;
  1         2  
  1         41  
290 1     1   6 use warnings;
  1         1  
  1         41  
291              
292 1     1   5 use File::Temp qw(tempfile);
  1         1  
  1         311  
293              
294             sub new {
295 2     2   5 my $proto = shift;
296 2   33     15 my $class = ref($proto) || $proto;
297              
298 2         38 my $self = { @_, first => 1 };
299              
300 2         12 $$self{fh} = tempfile(DIR => $$self{dir}, UNLINK => 1);
301              
302 2         1397 bless $self, $class;
303 2         10 return $self;
304             }
305              
306              
307             sub add {
308 4     4   33 my $self = shift;
309 4         6 my ($val) = @_;
310              
311 4         7 my $json_val = eval { $$self{json}->encode($val) };
  4         31  
312 4 50       14 if ($@) {
313 0         0 carp $@;
314 0         0 return;
315             }
316              
317 4 100       15 if ($$self{first}) {
318 2         4 $$self{first} = 0;
319 2         3 print { $$self{fh} } "[", $json_val;
  2         64  
320             } else {
321 2         4 print { $$self{fh} } ",", $json_val;
  2         7  
322             }
323             }
324              
325              
326             sub end {
327 2     2   13 my $self = shift;
328 2         4 my $fh = $$self{fh};
329 2         6 print $fh "]";
330 2         73 $fh->flush;
331 2         10 $fh->seek(0,0);
332 2         22 return $fh;
333             }
334              
335              
336              
337             package JSON::Builder::Obj;
338 1     1   6 use strict;
  1         2  
  1         43  
339 1     1   4 use warnings;
  1         2  
  1         27  
340              
341 1     1   46 use File::Temp qw(tempfile);
  1         2  
  1         391  
342              
343             sub new {
344 2     2   11 my $proto = shift;
345 2   33     13 my $class = ref($proto) || $proto;
346              
347 2         12 my $self = { @_, first => 1 };
348              
349 2         9 $$self{fh} = tempfile(DIR => $$self{dir}, UNLINK => 1);
350              
351 2         1370 bless $self, $class;
352 2         9 return $self;
353             }
354              
355              
356             sub add {
357 4     4   27 my $self = shift;
358 4         8 my ($key, $val) = @_;
359              
360 4         7 my $json_key = eval { $$self{json}->encode($key) };
  4         26  
361 4 50       12 if ($@) {
362 0         0 carp $@;
363 0         0 return;
364             }
365              
366 4         6 my $json_val = eval { $$self{json}->encode($val) };
  4         20  
367 4 50       142 if ($@) {
368 0         0 carp $@;
369 0         0 return;
370             }
371              
372 4 100       23 if ($$self{first}) {
373 2         9 $$self{first} = 0;
374 2         13 print { $$self{fh} } "{", $json_key, ":", $json_val;
  2         48  
375             } else {
376 2         3 print { $$self{fh} } ",", $json_key, ":", $json_val;
  2         9  
377             }
378             }
379              
380              
381             sub end {
382 2     2   10 my $self = shift;
383 2         4 my $fh = $$self{fh};
384 2         4 print $fh "}";
385 2         68 $fh->flush;
386 2         9 $fh->seek(0,0);
387 2         21 return $fh;
388             }
389              
390              
391              
392             package JSON::Builder::Compress; # Compress, Base64
393 1     1   6 use strict;
  1         1  
  1         194  
394 1     1   8 use warnings;
  1         2  
  1         172  
395 1     1   7 use base qw(JSON::Builder);
  1         2  
  1         369  
396              
397 1     1   13 use Compress::Zlib;
  1         2  
  1         623  
398 1     1   6 use MIME::Base64 qw(encode_base64url);
  1         1  
  1         794  
399              
400             sub init {
401 1     1   4 my $self = shift;
402 1         7 $$self{x} = deflateInit();
403 1         641 $$self{write_buf} = "";
404             }
405              
406              
407             sub write {
408 13     13   16 my $self = shift;
409              
410 13         22 my $buf = join "", @_;
411              
412 13         38 my ($output, $status) = $$self{x}->deflate($buf);
413 13 50       148 $status == Z_OK or die "deflation failed\n";
414              
415 13 100       73 if ($output) {
416 1         4 my $write_buf = join "", $$self{write_buf}, $output;
417 1         5 my $l = int(length($write_buf)/ 57) * 57;
418 1 50       10 if ($l) {
419 0         0 my $buf_head = substr $write_buf, 0, $l;
420 0         0 $$self{write_buf} = substr $write_buf, $l;
421 0         0 print { $$self{fh} } encode_base64url($buf_head, "");
  0         0  
422             } else {
423 1         4 $$self{write_buf} = $write_buf;
424             }
425             }
426             };
427              
428              
429             sub write_flush {
430 1     1   4 my $self = shift;
431              
432 1         7 my ($output, $status) = $$self{x}->flush();
433 1 50       102 $status == Z_OK or die "deflation failed\n";
434              
435 1 50       7 if ($output) {
436 1         3 $$self{write_buf} .= $output;
437             }
438              
439 1         2 print { $$self{fh} } encode_base64url($$self{write_buf}, "");
  1         6  
440              
441 1         32 $$self{write_buf} = "";
442             }
443              
444              
445             1;