File Coverage

blib/lib/Text/JSON/Nibble.pm
Criterion Covered Total %
statement 11 76 14.4
branch 0 30 0.0
condition 0 9 0.0
subroutine 4 10 40.0
pod 6 6 100.0
total 21 131 16.0


line stmt bran cond sub pod time code
1             package Text::JSON::Nibble;
2              
3             =encoding utf8
4              
5             =cut
6              
7 1     1   14311 use 5.006;
  1         4  
8 1     1   5 use strict;
  1         2  
  1         21  
9 1     1   4 use warnings;
  1         5  
  1         23  
10              
11 1     1   467 use Data::Dumper;
  1         7031  
  1         581  
12              
13             =head1 NAME
14              
15             Text::JSON::Nibble - Nibble complete JSON objects from buffers
16              
17             =head1 VERSION
18              
19             Version 1.00
20              
21             =cut
22              
23             our $VERSION = '1.00';
24              
25             =head1 WARNING
26              
27             This module should be used with caution, it will not handle 'badly formed' json well, its entire purpose was because I was experiencing
28             segfaults with Cpanel::XS's decode_prefix when dealing with a streaming socket buffer.
29              
30             =head1 DESCRIPTION
31              
32             This module is a 'character' crawling JSON extractor for plain TEXT, usable in both a 'streaming' or 'block' method, for when you need something that is not XS.
33              
34             It is particularly handy for when you want to deal with JSON without decoding it.
35              
36             =head1 SYNOPSIS
37              
38             use Text::JSON::Nibble;
39              
40             my $json = '{"lol":{"a":[1,2,3],"b":"lol"}}';
41             my $item = Text::JSON::Nibble->new();
42              
43             my @results = @{ $item->digest($json) };
44              
45             =head1 EXAMPLES
46              
47             =head2 Example1 (Basic usage)
48              
49             use Text::JSON::Nibble;
50              
51             my $json = '{"lol":{"a":[1,2,3],"b":"lol"}}{"lol":{"a":[1,2,3],"b":"lol"}}';
52             my $item = Text::JSON::Nibble->new();
53              
54             foreach my $jsonBlock ( @{ $item->digest($json) } ) {
55             print "Found: $jsonBlock\n";
56             }
57              
58             # Will display the following:
59             # Found: {"lol":{"a":[1,2,3],"b":"lol"}}
60             # Found: {"lol":{"a":[1,2,3],"b":"lol"}}
61            
62              
63             =head2 Example2 (Basic usage - mangled JSON)
64              
65             use Text::JSON::Nibble;
66              
67             my $json = '\cxa4GL
68             my $item = Text::JSON::Nibble->new();
69              
70             foreach my $jsonBlock ( @{ $item->digest($json) } ) {
71             print "Found: $jsonBlock\n";
72             }
73              
74             # Will display the following:
75             # Found: {"lol":{"a":[1,2,3],"b":"lol"}}
76             # Found: {"lol":{"a":[1,2,3],"b":"lol"}}
77              
78             =head2 Example3 (Streaming usage for POE and others)
79              
80             use Text::JSON::Nibble;
81            
82             my @jsonStream = qw( {"test":1} {"moreTest":2} {"part ial":3} );
83             my $item = Text::JSON::Nibble->new();
84            
85             $item->process( shift @jsonStream );
86              
87             while( $item->stack ) {
88             my $jsonBlock = $item->pull;
89             print "Found $jsonBlock\n";
90              
91             while ( my $newJSON = shift @jsonStream ) {
92             $item->process($newJSON);
93             }
94             }
95              
96             =head1 Generic callers
97              
98             =head2 new
99              
100             Generate a new JSON Nibble object
101              
102             =cut
103              
104             sub new {
105 0     0 1   my $class = shift;
106              
107             # Some private stuff for ourself
108 0           my $self = {
109             jsonqueue => [],
110             buffer => "",
111             iChar => [],
112             };
113            
114             # We are interested in characters of this code
115 0           $self->{iChar}->[91] = 1;
116 0           $self->{iChar}->[93] = 1;
117 0           $self->{iChar}->[123] = 1;
118 0           $self->{iChar}->[125] = 1;
119              
120             # Go with god my son
121 0           bless $self, $class;
122 0           return $self;
123             }
124              
125             =head1 Block functions
126              
127             =head2 digest
128              
129             Digest the text that is fed in and attempt to return a complete an array of JSON object from it, returns either a blank array or an array of text-encoded-json.
130              
131             Note you can call and use this at any time, even if you are using streaming functionality.
132              
133             =cut
134              
135             sub digest {
136 0     0 1   my $self = shift;
137 0           my $data = shift;
138              
139             # A place for our return
140 0           my $return = [];
141            
142             # If we got passed a blank data scalar just return failure
143 0 0         return $return if (!$data);
144              
145             # Save the current state for if we are dealing with a stream elsewhere.
146 0 0         my $stateBackup = $self->{state} if ($self->{state});
147            
148             # Load the digest data into the processor
149 0           $self->process($data);
150            
151             # Generate our results
152 0           while ($self->stack) { push @{$return},$self->pull }
  0            
  0            
153              
154             # Restore the previous state
155 0 0         $self->{state} = $stateBackup if ($stateBackup);
156              
157             # Process the data and return the result
158 0           return $return;
159             }
160              
161             =head1 Streaming functions
162              
163             =head2 process
164              
165             Load data into the buffer for json extraction, can be called at any point.
166              
167             This function will return the buffer length remaining after extraction has been attempted.
168              
169             This function takes 1 optional argument, text to be added to the buffer.
170              
171             =cut
172              
173             sub process {
174 0     0 1   my $self = shift;
175 0           my $data = shift;
176              
177             # Add any data present to the buffer, elsewhere return the length of what we have.
178 0 0         if ($data) { $self->{buffer} .= $data }
  0            
179 0           else { return length($self->{buffer}) }
180            
181             # If we have no buffer return 0.
182 0 0         if (!$self->{buffer}) { return 0 }
  0            
183            
184             # Load our state or establish a new one
185 0           my $state;
186 0 0         if ( $self->{state} ) {
187 0           $state = $self->{state};
188             } else {
189 0           $state = {
190             'typeAOpen' => 0,
191             'typeBOpen' => 0,
192             'arrayPlace' => 0,
193             'prevChar' => 32
194             };
195             }
196            
197             # Extract the new information into an array split by char
198 0           my @jsonText = split(//,$data);
199            
200             # Where to shorten the buffer to if we make extractions
201 0           my $breakPoint;
202              
203             # Loop over the text looking for json objects
204 0           foreach my $chr ( @jsonText ) {
205             # Find the code for the current character
206 0           my $charCode = ord($chr);
207              
208             # Check if this character is an escape \, if not check if its [ { } or ]
209 0 0 0       if ( $state->{prevChar} != 92 && $self->{iChar}->[$charCode] ) {
210             # Handle { } type brackets
211 0 0         if ( $charCode == 123 ) { $state->{typeAOpen}++ }
  0 0          
    0          
    0          
212 0           elsif ( $charCode == 125 ) { $state->{typeAOpen}-- }
213              
214             # Handle [ ] type brackets
215 0           elsif ( $charCode == 91 ) { $state->{typeBOpen}++ }
216 0           elsif ( $charCode == 93 ) { $state->{typeBOpen}-- }
217            
218             # Mark we have found something to start with
219 0 0         if (!defined $state->{arrayStart}) { $state->{arrayStart} = $state->{arrayPlace} }
  0            
220             }
221              
222             # If we have a complete object then leave
223 0 0 0       if ( defined $state->{arrayStart} && !$state->{typeAOpen} && !$state->{typeBOpen} ) {
      0        
224             # Ok we had a JSON object fully open and closed.
225             # push it into a return
226 0           push @{$self->{jsonqueue}},substr($self->{buffer},$state->{arrayStart},$state->{arrayPlace}+1-$state->{arrayStart});
  0            
227 0           delete $state->{arrayStart};
228 0           $breakPoint = $state->{arrayPlace};
229             }
230            
231             # Increment our arrayplace
232 0           $state->{arrayPlace}++;
233            
234             # Remember the last char
235 0           $state->{prevChar} = $charCode;
236             }
237            
238             # Clean up the arrayPlace and save state
239 0 0         if ($breakPoint) {
240 0           $self->{buffer} = substr($self->{buffer},$breakPoint+1);
241 0           $state->{arrayPlace} -= $breakPoint+1;
242             }
243            
244             # Save our state
245 0           $self->{state} = $state;
246            
247             # Return the remaining buffer size
248 0           return length($self->{buffer});
249             }
250              
251             =head2 stack
252              
253             Return the amount of succesfully extracted JSON blocks ready to be pulled.
254              
255             If no JSON blocks are ready, returns 0.
256              
257             This function takes no arguments.
258              
259             =cut
260              
261             sub stack {
262 0     0 1   my $self = shift;
263 0           return scalar( @{ $self->{jsonqueue} } );
  0            
264             }
265              
266             =head2 pull
267              
268             Pull an item from the stack, shortening the stack by 1.
269              
270             This function will return "" if the stack is empty.
271              
272             This function takes no arguments.
273              
274             =cut
275              
276             sub pull {
277 0     0 1   my $self = shift;
278            
279 0 0         if ( $self->stack == 0 ) { return "" }
  0            
280 0           return shift @{ $self->{jsonqueue} };
  0            
281             }
282              
283             =head2 reset
284              
285             Effectively flushs the objects buffers, giving you a clean object, this can be handy when you want to start processing from another stream.
286              
287             This function returns nothing.
288              
289             This function takes no arguments.
290              
291             =cut
292              
293             sub reset {
294 0     0 1   my $self = shift;
295            
296 0           $self->{jsonqueue} = [];
297 0           $self->{buffer} => "";
298             }
299              
300             =head1 AUTHOR
301              
302             Paul G Webster, C<< >>
303              
304             =head1 BUGS
305              
306             Please report any bugs or feature requests to C, or through
307             the web interface at L. I will be notified, and then you'll
308             automatically be notified of progress on your bug as I make changes.
309              
310              
311              
312              
313             =head1 SUPPORT
314              
315             You can find documentation for this module with the perldoc command.
316              
317             perldoc Text::JSON::Nibble
318              
319              
320             You can also look for information at:
321              
322             =over 4
323              
324             =item * The author publishs this module to GitLab (Please report bugs here)
325              
326             L
327              
328             =item * RT: CPAN's request tracker
329              
330             L
331              
332             =item * AnnoCPAN: Annotated CPAN documentation
333              
334             L
335              
336             =item * CPAN Ratings
337              
338             L
339              
340             =item * Search CPAN
341              
342             L
343              
344             =back
345              
346              
347             =head1 ACKNOWLEDGEMENTS
348              
349              
350             =head1 LICENSE AND COPYRIGHT
351              
352             Copyright 2017 Paul G Webster.
353              
354             This program is released under the following license: BSD
355              
356              
357             =cut
358              
359             1; # End of Text::JSON::Nibble