File Coverage

blib/lib/Text/JSON/Nibble.pm
Criterion Covered Total %
statement 8 76 10.5
branch 0 32 0.0
condition 0 9 0.0
subroutine 3 10 30.0
pod 6 7 85.7
total 17 134 12.6


line stmt bran cond sub pod time code
1             package Text::JSON::Nibble;
2              
3 1     1   13342 use 5.006;
  1         2  
4 1     1   3 use strict;
  1         1  
  1         14  
5 1     1   3 use warnings;
  1         4  
  1         476  
6              
7             =head1 NAME
8              
9             Text::JSON::Nibble - Nibble complete JSON objects from buffers
10              
11             =head1 VERSION
12              
13             Version 0.05
14              
15             =cut
16              
17             our $VERSION = '0.05';
18              
19              
20             =head1 SYNOPSIS
21              
22             =head2 Example1 (Basic usage)
23              
24             The basic usage of the module, Give it a stream of JSON in text form, it will extract the first complete block of JSON it finds.
25              
26             use warnings;
27             use strict;
28              
29             use Text::JSON::Nibble;
30             use JSON::MaybeXS;
31              
32             my $json = JSON->new;
33             my $item = Text::JSON::Nibble->new();
34              
35             my $test = {
36             lol => {
37             a => [1,2,3],
38             b => "lol"
39             }
40             };
41              
42             my $jsontext = $json->encode($test);
43              
44             $jsontext = "$jsontext$jsontext";
45              
46             print "jsontext: $jsontext\n";
47              
48             my ($text,$offset) = $item->digest($jsontext);
49              
50             print "Text: $text\n";
51             print "Offset: $offset\n";
52              
53             # Beware the offset is a real offset so the first character is classed as 0, the literal length is 1 greater;
54             $jsontext = substr($jsontext,$offset+1);
55              
56             print "new jsontext: $jsontext\n";
57              
58             =head2 Example2 (Streaming usage)
59              
60             This is a more efficient version for dealing with sockets that can be connected for a long period, NOTE this is not a
61             copy/paste example, please read carefully.
62              
63             use warnings;
64             use strict;
65              
66             use Text::JSON::Nibble;
67             use POE,ASYNC,...
68            
69             sub handler_for_when_socket_connected {
70             $mystash->{nibbler} = Text::JSON::Nibble->new();
71             }
72            
73             sub handler_tor_data_recvd {
74             $mystash->{nibbler}->load($information_recvd);
75              
76             # queue will return 0 when it has nothing
77             while ($mystash->{nibbler}->queue) {
78             # Grab the first item from the queue
79             my $jsonChunk = $mystash->{nibbler}->pull;
80            
81             # Do something with it
82             $superCommand->magic($jsonChunk);
83             }
84             }
85              
86              
87              
88             =head1 WARNING
89              
90             This module should be used with caution, it will not handle 'badly formed' json well, its entire purpose was because I was experiencing
91             segfaults with Cpanel::XS's decode_prefix when dealing with a streamnig socket buffer.
92              
93             Use this only when needed.
94              
95             =head1 SUBROUTINES/METHODS
96              
97             =head2 new
98              
99             Generate a new JSON Nibble object
100              
101             =cut
102              
103             sub new {
104 0     0 1   my $class = shift;
105              
106 0           my $self = {
107             jsonqueue => [],
108             buffer => "",
109             };
110              
111 0           bless $self, $class;
112              
113 0           return $self;
114             }
115              
116             =head2 digest
117              
118             Digest the text that is fed and attempt to return a complete JSON object from it, returns two items the JSON object (in text form) and the offset in the buffer.
119              
120             On a failure it will return "" and 0
121              
122             =cut
123              
124             sub digest {
125 0     0 1   my $self = shift;
126 0           my $data = shift;
127              
128             # If we got passed a blank data scalar just return failure
129 0 0         return ("",0) if (!$data);
130              
131             # Process the data and return the result
132 0           return $self->proc(1,$data);
133             }
134              
135             =head2 load
136              
137             Load information into the buffer for processing
138              
139             =cut
140              
141             sub load {
142 0     0 1   my $self = shift;
143 0           my $data = shift;
144            
145 0           $self->{buffer} .= $data;
146            
147 0 0         if (!$self->{state}) {
148 0           $self->{state} = [0,0,"",0];
149             }
150            
151 0           $self->proc();
152             }
153              
154             =head2 queue
155              
156             Return how many objects are in the queue
157              
158             =cut
159              
160             sub queue {
161 0     0 1   my $self = shift;
162              
163 0           return scalar( @{ $self->{jsonqueue} } );
  0            
164             }
165              
166             =head2 pull
167              
168             Pull the first object from the queue
169              
170             =cut
171              
172             sub pull {
173 0     0 1   my $self = shift;
174              
175 0 0         return if (! queue($self) );
176            
177 0           return shift( @{ $self->{jsonqueue} } );
  0            
178             }
179              
180             =head2 clear
181              
182             Clear the internal state and buffer state of nibble, like a brand new() object.
183              
184             =cut
185              
186             sub clear {
187 0     0 1   my $self = shift;
188              
189 0           $self->{jsonqueue} = [];
190 0           $self->{state} = [0,0,"",0];
191 0           $self->{buffer} = "";
192             }
193              
194             =head2 _proc
195              
196             Proccess text into json (Do not call this directly)
197              
198             =cut
199              
200             sub proc {
201 0     0 0   my $self = shift;
202 0           my $return = shift;
203              
204             # Create a place to store the JSON object and a few tracking scalars
205 0           my @jsonArray;
206 0           my $typeAOpen = 0;
207 0           my $typeBOpen = 0;
208 0           my $arrayPlace = 0;
209 0           my $prevChar = "";
210              
211             # Which mode are we operating in? if we have to return we are in simple mode
212 0 0         if ($return) {
213             # Grab data from the call rather than our buffer
214 0           my $data = shift;
215            
216             # Return nothing if the buffer is empty
217 0 0         if (!$data) { return ("",0) }
  0            
218              
219 0           @jsonArray = split(//,$data);
220             } else {
221             # Return nothing if the buffer is empty
222 0 0         if (!$self->{buffer}) { return }
  0            
223              
224             # Holder for temporary pos
225 0           my $pos = 0;
226            
227             # Ok lets use the place we read up to last time as a starting point
228 0           ($typeAOpen,$typeBOpen,$prevChar,$pos) = @{ $self->{state} };
  0            
229            
230             # Create the JSON array
231 0           @jsonArray = split(//,substr($self->{buffer},$pos));
232             }
233              
234             # Crawl through the array counting our open data brackets
235 0           foreach my $chr ( @jsonArray ) {
236 0 0         if ($arrayPlace > 0) { $prevChar = $jsonArray[$arrayPlace - 1] }
  0            
237              
238 0 0         if ( ord($prevChar) != 92 ) {
239 0           my $charCode = ord($chr);
240              
241             # Handle { } type brackets
242 0 0         if ( $charCode == 123 ) { $typeAOpen++ }
  0 0          
243 0           elsif ( $charCode == 125 ) { $typeAOpen-- }
244              
245             # Handle [ ] type brackets
246 0 0         if ( $charCode == 91 ) { $typeBOpen++ }
  0 0          
247 0           elsif ( $charCode == 93 ) { $typeBOpen-- }
248             }
249              
250             # If we have a complete object then leave
251 0 0 0       if ( $arrayPlace > 1 && !$typeAOpen && !$typeBOpen ) {
      0        
252             # Convert the offset into a length
253 0           last;
254             }
255 0           else { $arrayPlace++ }
256             }
257              
258 0 0 0       if ( !$typeAOpen && !$typeBOpen ) {
    0          
259 0 0         if ( $return ) {
260 0           return ( join('',@jsonArray[0..$arrayPlace]), $arrayPlace );
261             } else {
262             # Add the complete object to the queue
263 0           push( @{$self->{jsonqueue}}, substr($self->{buffer},0,$arrayPlace+1) );
  0            
264            
265             # Adjust the buffer
266 0           $self->{buffer} = substr($self->{buffer},$arrayPlace+1);
267            
268             # Reset the states
269 0           $self->{state} = [0,0,"",0];
270            
271             # Perhaps we receieved more than one complete JSON object in the last addition, lets check!
272 0           return $self->proc();
273             }
274             } elsif ($return) {
275             # If we got no match, lets remember our state so that if any more data is loaded we do not need to re-read it all.
276 0           return ("",0);
277             } else {
278             # We are in stream mode and we did not match jack, so store our states
279 0           $self->{state} = [$typeAOpen,$typeBOpen,$prevChar,$arrayPlace];
280            
281             # And return
282 0           return;
283             }
284             }
285              
286             =head1 AUTHOR
287              
288             Paul G Webster, C<< >>
289              
290             =head1 BUGS
291              
292             Please report any bugs or feature requests to C, or through
293             the web interface at L. I will be notified, and then you'll
294             automatically be notified of progress on your bug as I make changes.
295              
296              
297              
298              
299             =head1 SUPPORT
300              
301             You can find documentation for this module with the perldoc command.
302              
303             perldoc Text::JSON::Nibble
304              
305              
306             You can also look for information at:
307              
308             =over 4
309              
310             =item * The author publishs this module to GitLab (Please report bugs here)
311              
312             L
313              
314             =item * RT: CPAN's request tracker
315              
316             L
317              
318             =item * AnnoCPAN: Annotated CPAN documentation
319              
320             L
321              
322             =item * CPAN Ratings
323              
324             L
325              
326             =item * Search CPAN
327              
328             L
329              
330             =back
331              
332              
333             =head1 ACKNOWLEDGEMENTS
334              
335              
336             =head1 LICENSE AND COPYRIGHT
337              
338             Copyright 2017 Paul G Webster.
339              
340             This program is released under the following license: BSD
341              
342              
343             =cut
344              
345             1; # End of Text::JSON::Nibble