File Coverage

lib/JSON/LINQ.pm
Criterion Covered Total %
statement 619 800 77.3
branch 205 356 57.5
condition 35 78 44.8
subroutine 119 148 80.4
pod 59 61 96.7
total 1037 1443 71.8


line stmt bran cond sub pod time code
1             package JSON::LINQ;
2             ######################################################################
3             #
4             # JSON::LINQ - LINQ-style query interface for JSON and JSONL files
5             #
6             # https://metacpan.org/dist/JSON-LINQ
7             #
8             # Copyright (c) 2026 INABA Hitoshi
9             ######################################################################
10             #
11             # Compatible : Perl 5.005_03 and later
12             # Platform : Windows and UNIX/Linux
13             #
14             ######################################################################
15              
16 7     7   249039 use 5.00503; # Universal Consensus 1998 for primetools
  7         28  
17             # Perl 5.005_03 compatibility for historical toolchains
18             # use 5.008001; # Lancaster Consensus 2013 for toolchains
19              
20 7     7   41 use strict;
  7         42  
  7         695  
21 7 50 33 7   256 BEGIN { if ($] < 5.006 && !defined(&warnings::import)) { $INC{'warnings.pm'} = 'stub'; eval 'package warnings; sub import {}' } }
  0         0  
  0         0  
22 7     7   50 use warnings; local $^W = 1;
  7         11  
  7         635  
23 7 50   7   312 BEGIN { pop @INC if $INC[-1] eq '.' }
24              
25 7     7   90 use Carp qw(croak);
  7         14  
  7         536  
26              
27 7     7   40 use vars qw($VERSION);
  7         11  
  7         747  
28             $VERSION = '1.00';
29             $VERSION = $VERSION;
30             # $VERSION self-assignment suppresses "used only once" warning under strict.
31              
32             ###############################################################################
33             # JSON Boolean type objects (merged from mb::JSON)
34             ###############################################################################
35              
36             package JSON::LINQ::Boolean;
37 7     7   44 use vars qw($VERSION);
  7         11  
  7         1043  
38             $VERSION = '1.00';
39             $VERSION = $VERSION;
40              
41             use overload
42 2     2   4 '0+' => sub { ${ $_[0] } },
  2         9  
43 2 100   2   3 q{""} => sub { ${ $_[0] } ? 'true' : 'false' },
  2         8  
44 6     6   85 'bool' => sub { ${ $_[0] } },
  6         22  
45 7     7   4081 fallback => 1;
  7         12615  
  7         108  
46              
47             package JSON::LINQ;
48              
49 7     7   876 use vars qw($true $false);
  7         14  
  7         95608  
50             {
51             my $_t = 1; $true = bless \$_t, 'JSON::LINQ::Boolean';
52             my $_f = 0; $false = bless \$_f, 'JSON::LINQ::Boolean';
53             }
54              
55 2     2 1 62 sub true { $true }
56 2     2 1 24 sub false { $false }
57              
58             ###############################################################################
59             # Internal JSON encoder/decoder (merged from mb::JSON 0.06)
60             ###############################################################################
61              
62             # UTF-8 multibyte pattern
63             my $utf8_pat = join '|', (
64             '[\x00-\x7F\x80-\xBF\xC0-\xC1\xF5-\xFF]',
65             '[\xC2-\xDF][\x80-\xBF]',
66             '[\xE0][\xA0-\xBF][\x80-\xBF]',
67             '[\xE1-\xEC][\x80-\xBF][\x80-\xBF]',
68             '[\xED][\x80-\x9F][\x80-\xBF]',
69             '[\xEE-\xEF][\x80-\xBF][\x80-\xBF]',
70             '[\xF0][\x90-\xBF][\x80-\xBF][\x80-\xBF]',
71             '[\xF1-\xF3][\x80-\xBF][\x80-\xBF][\x80-\xBF]',
72             '[\xF4][\x80-\x8F][\x80-\xBF][\x80-\xBF]',
73             '[\x00-\xFF]',
74             );
75              
76             sub _json_decode {
77 41 50   41   120 my $json = defined $_[0] ? $_[0] : $_;
78 41         75 my $r = \$json;
79 41         113 my $val = _parse_value($r);
80 41         84 $$r =~ s/\A\s+//s;
81 41 50       117 croak "JSON::LINQ::_json_decode: trailing garbage: " . substr($$r, 0, 20)
82             if length $$r;
83 41         104 return $val;
84             }
85              
86             sub _parse_value {
87 250     250   458 my ($r) = @_;
88 250         625 $$r =~ s/\A\s+//s;
89 250 50       577 croak "JSON::LINQ::_json_decode: unexpected end of input" unless length $$r;
90              
91 250         542 my $c = substr($$r, 0, 1);
92              
93 250 100       1418 if ($c eq '{') { return _parse_object($r) }
  67 100       151  
    100          
    100          
    100          
    100          
    50          
94 14         43 elsif ($c eq '[') { return _parse_array($r) }
95 85         178 elsif ($c eq '"') { return _parse_string($r) }
96 1         5 elsif ($$r =~ s/\Anull(?=[^a-zA-Z0-9_]|$)//s) { return undef }
97 12         60 elsif ($$r =~ s/\Atrue(?=[^a-zA-Z0-9_]|$)//s) { return $true }
98 6         20 elsif ($$r =~ s/\Afalse(?=[^a-zA-Z0-9_]|$)//s) { return $false }
99             elsif ($$r =~ s/\A(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)//s) {
100 65         272 return $1 + 0;
101             }
102             else {
103 0         0 croak "JSON::LINQ::_json_decode: unexpected token: " . substr($$r, 0, 20);
104             }
105             }
106              
107             sub _parse_object {
108 67     67   141 my ($r) = @_;
109 67         240 $$r =~ s/\A\{//s;
110 67         125 my %obj;
111 67         143 $$r =~ s/\A\s+//s;
112 67 50       185 if ($$r =~ s/\A\}//s) { return { %obj } }
  0         0  
113 67         103 while (1) {
114 169         334 $$r =~ s/\A\s+//s;
115 169 50       529 croak "JSON::LINQ::_json_decode: expected string key in object"
116             unless $$r =~ /\A"/;
117 169         379 my $key = _parse_string($r);
118 169         382 $$r =~ s/\A\s+//s;
119 169 50       579 $$r =~ s/\A://s
120             or croak "JSON::LINQ::_json_decode: expected ':' after key '$key'";
121 169         443 my $val = _parse_value($r);
122 169         434 $obj{$key} = $val;
123 169         415 $$r =~ s/\A\s+//s;
124 169 100       669 if ($$r =~ s/\A,//s) { next }
  102 50       194  
125 67         175 elsif ($$r =~ s/\A\}//s) { last }
126 0         0 else { croak "JSON::LINQ::_json_decode: expected ',' or '}' in object" }
127             }
128 67         412 return { %obj };
129             }
130              
131             sub _parse_array {
132 14     14   29 my ($r) = @_;
133 14         75 $$r =~ s/\A\[//s;
134 14         27 my @arr;
135 14         37 $$r =~ s/\A\s+//s;
136 14 50       1154 if ($$r =~ s/\A\]//s) { return [ @arr ] }
  0         0  
137 14         26 while (1) {
138 40         113 push @arr, _parse_value($r);
139 40         126 $$r =~ s/\A\s+//s;
140 40 100       197 if ($$r =~ s/\A,//s) { next }
  26 50       48  
141 14         26 elsif ($$r =~ s/\A\]//s) { last }
142 0         0 else { croak "JSON::LINQ::_json_decode: expected ',' or ']' in array" }
143             }
144 14         67 return [ @arr ];
145             }
146              
147             my %UNESC = (
148             '"' => '"', '\\' => '\\', '/' => '/',
149             'b' => "\x08", 'f' => "\x0C",
150             'n' => "\n", 'r' => "\r", 't' => "\t",
151             );
152              
153             sub _parse_string {
154 254     254   451 my ($r) = @_;
155 254         859 $$r =~ s/\A"//s;
156 254         462 my $s = '';
157 254         357 while (1) {
158 1174 100       9331 if ($$r =~ s/\A"//s) { last }
  254 50       590  
    50          
    50          
159 0         0 elsif ($$r =~ s/\A\\(["\\\/bfnrt])//s) { $s .= $UNESC{$1} }
160             elsif ($$r =~ s/\A\\u([0-9a-fA-F]{4})//s) {
161 0         0 $s .= _cp_to_utf8(hex($1));
162             }
163 920         2151 elsif ($$r =~ s/\A($utf8_pat)//s) { $s .= $1 }
164 0         0 else { croak "JSON::LINQ::_json_decode: unterminated string" }
165             }
166 254         783 return $s;
167             }
168              
169             sub _cp_to_utf8 {
170 0     0   0 my ($cp) = @_;
171 0 0       0 return chr($cp) if $cp <= 0x7F;
172 0 0       0 if ($cp <= 0x7FF) {
173 0         0 return chr(0xC0|($cp>>6)) . chr(0x80|($cp&0x3F));
174             }
175 0         0 return chr(0xE0|($cp>>12))
176             . chr(0x80|(($cp>>6)&0x3F))
177             . chr(0x80|($cp&0x3F));
178             }
179              
180             sub _json_encode {
181 4     4   9 my ($data) = @_;
182 4         10 return _enc_value($data);
183             }
184              
185             sub _enc_value {
186 12     12   27 my ($v) = @_;
187 12 50       26 return 'null' unless defined $v;
188 12 0       28 if (ref $v eq 'JSON::LINQ::Boolean') { return $$v ? 'true' : 'false' }
  0 50       0  
189 12 50       25 if (ref $v eq 'ARRAY') { return '[' . join(',', map { _enc_value($_) } @$v) . ']' }
  0         0  
  0         0  
190 12 100       26 if (ref $v eq 'HASH') {
191 4         19 my @pairs = map { _enc_string($_) . ':' . _enc_value($v->{$_}) }
  8         18  
192             sort keys %$v;
193 4         30 return '{' . join(',', @pairs) . '}';
194             }
195             # number: matches JSON number pattern exactly
196 8 100 66     62 if (!ref $v && $v =~ /\A-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?\z/s) {
197 4         46 return $v;
198             }
199 4         11 return _enc_string($v);
200             }
201              
202             sub _enc_string {
203 12     12   24 my ($s) = @_;
204 12         24 $s =~ s/\\/\\\\/g;
205 12         20 $s =~ s/"/\\"/g;
206 12         19 $s =~ s/\x08/\\b/g;
207 12         21 $s =~ s/\x0C/\\f/g;
208 12         19 $s =~ s/\n/\\n/g;
209 12         18 $s =~ s/\r/\\r/g;
210 12         21 $s =~ s/\t/\\t/g;
211 12         26 $s =~ s/([\x00-\x1F])/sprintf('\\u%04X', ord($1))/ge;
  0         0  
212 12         59 return '"' . $s . '"';
213             }
214              
215             ###############################################################################
216             # Constructor and Iterator Infrastructure
217             ###############################################################################
218              
219             sub new {
220 171     171 0 352 my($class, $iterator) = @_;
221 171         1160 return bless { iterator => $iterator }, $class;
222             }
223              
224             sub iterator {
225 204     204 0 321 my $self = $_[0];
226             # If this object was created by _from_snapshot, _factory provides
227             # a fresh iterator closure each time iterator() is called.
228 204 100       577 if (exists $self->{_factory}) {
229 34         59 return $self->{_factory}->();
230             }
231 170         338 return $self->{iterator};
232             }
233              
234             ###############################################################################
235             # Data Source Methods
236             ###############################################################################
237              
238             # From - create query from array
239             sub From {
240 90     90 1 936072 my($class, $source) = @_;
241              
242 90 50       278 if (ref($source) eq 'ARRAY') {
243 90         146 my $i = 0;
244             return $class->new(sub {
245 429 100   429   861 return undef if $i >= scalar(@$source);
246 348         716 return $source->[$i++];
247 90         366 });
248             }
249              
250 0         0 die "From() requires ARRAY reference";
251             }
252              
253             # FromJSON - read a JSON file containing a top-level array of objects
254             # Each element of the array becomes one item in the sequence.
255             # The file must contain a single JSON array: [ {...}, {...}, ... ]
256             # or a single JSON object (treated as a one-element sequence).
257             sub FromJSON {
258 8     8 1 220591 my($class, $file) = @_;
259              
260 8         24 my $fh;
261 8 50       27 if ($] >= 5.006) {
262 8 50       806 eval q{ open($fh, '<', $file) } or die "Cannot open '$file': $!";
263             }
264             else {
265 0         0 $fh = \do { local *_ };
  0         0  
266 0 0       0 open($fh, "< $file") or die "Cannot open '$file': $!";
267             }
268 8         62 binmode $fh; # Raw bytes; handles UTF-8 without UTF-8 flag
269              
270 8         13 my $content = do { local $/; <$fh> };
  8         48  
  8         298  
271 8         193 close $fh;
272              
273 8         19 my $data = eval { _json_decode($content) };
  8         35  
274 8 50       1261 die "JSON::LINQ::FromJSON: cannot parse '$file': $@" if $@;
275              
276 8         19 my $records;
277 8 50       24 if (ref($data) eq 'ARRAY') {
    0          
278 8         29 $records = $data;
279             }
280             elsif (ref($data) eq 'HASH') {
281 0         0 $records = [ $data ];
282             }
283             else {
284 0         0 die "JSON::LINQ::FromJSON: '$file' must contain a JSON array or object";
285             }
286              
287 8         12 my $i = 0;
288             return $class->new(sub {
289 30 100   30   76 return undef if $i >= scalar(@$records);
290 22         59 return $records->[$i++];
291 8         121 });
292             }
293              
294             # FromJSONL - read a JSONL (JSON Lines) file
295             # Each line is a separate JSON value (typically an object).
296             # Empty lines and lines beginning with '#' are skipped.
297             # This is memory-efficient for large files: one line at a time.
298             sub FromJSONL {
299 8     8 1 839 my($class, $file) = @_;
300              
301 8         16 my $fh;
302 8 50       20 if ($] >= 5.006) {
303 8 50       742 eval q{ open($fh, '<', $file) } or die "Cannot open '$file': $!";
304             }
305             else {
306 0         0 $fh = \do { local *_ };
  0         0  
307 0 0       0 open($fh, "< $file") or die "Cannot open '$file': $!";
308             }
309 8         59 binmode $fh; # Raw bytes; handles UTF-8 without UTF-8 flag
310              
311             return $class->new(sub {
312 33     33   445 while (my $line = <$fh>) {
313 27         59 chomp $line;
314 27         60 $line =~ s/\r\z//; # Strip CR for CRLF files
315 27 100       59 next unless length $line;
316 26 50       107 next if $line =~ /\A\s*\z/; # Skip blank lines
317 26 50       72 next if $line =~ /\A\s*#/; # Skip comment lines
318              
319 26         48 my $val = eval { _json_decode($line) };
  26         57  
320 26 50       61 if ($@) {
321 0         0 warn "JSON::LINQ::FromJSONL: skipping invalid JSON line: $@";
322 0         0 next;
323             }
324 26         87 return $val;
325             }
326 7         97 close $fh;
327 7         24 return undef;
328 8         103 });
329             }
330              
331             # FromJSONString - create query from a JSON string (array or object)
332             sub FromJSONString {
333 7     7 1 207 my($class, $json) = @_;
334              
335 7         15 my $data = eval { _json_decode($json) };
  7         32  
336 7 50       20 die "JSON::LINQ::FromJSONString: cannot parse JSON: $@" if $@;
337              
338 7         15 my $records;
339 7 100       48 if (ref($data) eq 'ARRAY') {
    50          
340 6         13 $records = $data;
341             }
342             elsif (ref($data) eq 'HASH') {
343 1         2 $records = [ $data ];
344             }
345             else {
346 0         0 $records = [ $data ];
347             }
348              
349 7         13 my $i = 0;
350             return $class->new(sub {
351 26 100   26   72 return undef if $i >= scalar(@$records);
352 19         54 return $records->[$i++];
353 7         54 });
354             }
355              
356             # Range - generate sequence of integers
357             sub Range {
358 1     1 1 23 my($class, $start, $count) = @_;
359              
360 1         3 my $current = $start;
361 1         2 my $remaining = $count;
362              
363             return $class->new(sub {
364 6 100   6   16 return undef if $remaining <= 0;
365 5         8 $remaining--;
366 5         12 return $current++;
367 1         7 });
368             }
369              
370             # Empty - return empty sequence
371             sub Empty {
372 4     4 1 40 my($class) = @_;
373              
374             return $class->new(sub {
375 5     5   8 return undef;
376 4         14 });
377             }
378              
379             # Repeat - repeat element specified number of times
380             sub Repeat {
381 0     0 1 0 my($class, $element, $count) = @_;
382              
383 0         0 my $remaining = $count;
384              
385             return $class->new(sub {
386 0 0   0   0 return undef if $remaining <= 0;
387 0         0 $remaining--;
388 0         0 return $element;
389 0         0 });
390             }
391              
392             ###############################################################################
393             # Filtering Methods
394             ###############################################################################
395              
396             # Where - filter elements
397             sub Where {
398 10     10 1 33 my($self, @args) = @_;
399 10         30 my $iter = $self->iterator;
400 10         23 my $class = ref($self);
401              
402             # Support both code reference and DSL form
403 10         16 my $cond;
404 10 100 66     76 if (@args == 1 && ref($args[0]) eq 'CODE') {
405 6         16 $cond = $args[0];
406             }
407             else {
408             # DSL form: Where(key => value, ...)
409 4         11 my %match = @args;
410             $cond = sub {
411 20     20   30 my $row = shift;
412 20         64 for my $k (keys %match) {
413 23 100       59 return 0 unless defined $row->{$k};
414 18 100       77 return 0 unless $row->{$k} eq $match{$k};
415             }
416 8         48 return 1;
417 4         16 };
418             }
419              
420             return $class->new(sub {
421 33     33   62 while (1) {
422 55         148 my $item = $iter->();
423 55 100       130 return undef unless defined $item;
424 45 100       102 return $item if $cond->($item);
425             }
426 10         80 });
427             }
428              
429             ###############################################################################
430             # Projection Methods
431             ###############################################################################
432              
433             # Select - transform elements
434             sub Select {
435 21     21 1 44 my($self, $selector) = @_;
436 21         51 my $iter = $self->iterator;
437 21         65 my $class = ref($self);
438              
439             return $class->new(sub {
440 104     104   185 my $item = $iter->();
441 104 100       1299 return undef unless defined $item;
442 83         171 return $selector->($item);
443 21         130 });
444             }
445              
446             # SelectMany - flatten sequences
447             sub SelectMany {
448 2     2 1 5 my($self, $selector) = @_;
449 2         6 my $iter = $self->iterator;
450 2         4 my $class = ref($self);
451              
452 2         3 my @buffer;
453              
454             return $class->new(sub {
455 12     12   16 while (1) {
456 18 100       30 if (@buffer) {
457 10         21 return shift @buffer;
458             }
459              
460 8         25 my $item = $iter->();
461 8 100       18 return undef unless defined $item;
462              
463 6         16 my $result = $selector->($item);
464 6 50       24 unless (ref($result) eq 'ARRAY') {
465 0         0 die "SelectMany: selector must return an ARRAY reference";
466             }
467 6         15 @buffer = @$result;
468             }
469 2         10 });
470             }
471              
472             # Concat - concatenate two sequences
473             sub Concat {
474 1     1 1 17 my($self, $second) = @_;
475 1         3 my $class = ref($self);
476              
477 1         6 my $first_iter = $self->iterator;
478 1         2 my $second_iter;
479 1         2 my $first_done = 0;
480              
481             return $class->new(sub {
482 9 100   9   12 if (!$first_done) {
483 5         7 my $item = $first_iter->();
484 5 100       7 if (defined $item) {
485 4         10 return $item;
486             }
487 1         19 $first_done = 1;
488 1         3 $second_iter = $second->iterator;
489             }
490              
491 5 50       8 return $second_iter ? $second_iter->() : undef;
492 1         9 });
493             }
494              
495             # Zip - combine two sequences element-wise
496             sub Zip {
497 0     0 1 0 my($self, $second, $result_selector) = @_;
498              
499 0         0 my $iter1 = $self->iterator;
500 0         0 my $iter2 = $second->iterator;
501 0         0 my $class = ref($self);
502              
503             return $class->new(sub {
504 0     0   0 my $item1 = $iter1->();
505 0         0 my $item2 = $iter2->();
506              
507             # Return undef if either sequence ends
508 0 0 0     0 return undef unless defined($item1) && defined($item2);
509              
510 0         0 return $result_selector->($item1, $item2);
511 0         0 });
512             }
513              
514             ###############################################################################
515             # Partitioning Methods
516             ###############################################################################
517              
518             # Take - take first N elements
519             sub Take {
520 3     3 1 9 my($self, $count) = @_;
521 3         15 my $iter = $self->iterator;
522 3         27 my $class = ref($self);
523 3         8 my $taken = 0;
524              
525             return $class->new(sub {
526 10 100   10   26 return undef if $taken >= $count;
527 7         17 my $item = $iter->();
528 7 50       64 return undef unless defined $item;
529 7         13 $taken++;
530 7         22 return $item;
531 3         27 });
532             }
533              
534             # Skip - skip first N elements
535             sub Skip {
536 1     1 1 3 my($self, $count) = @_;
537 1         3 my $iter = $self->iterator;
538 1         3 my $class = ref($self);
539 1         2 my $skipped = 0;
540              
541             return $class->new(sub {
542 4     4   10 while ($skipped < $count) {
543 2         6 my $item = $iter->();
544 2 50       17 return undef unless defined $item;
545 2         6 $skipped++;
546             }
547 4         9 return $iter->();
548 1         6 });
549             }
550              
551             # TakeWhile - take while condition is true
552             sub TakeWhile {
553 0     0 1 0 my($self, $predicate) = @_;
554 0         0 my $iter = $self->iterator;
555 0         0 my $class = ref($self);
556 0         0 my $done = 0;
557              
558             return $class->new(sub {
559 0 0   0   0 return undef if $done;
560 0         0 my $item = $iter->();
561 0 0       0 return undef unless defined $item;
562              
563 0 0       0 if ($predicate->($item)) {
564 0         0 return $item;
565             }
566             else {
567 0         0 $done = 1;
568 0         0 return undef;
569             }
570 0         0 });
571             }
572              
573             # SkipWhile - skip elements while predicate is true
574             sub SkipWhile {
575 0     0 1 0 my($self, $predicate) = @_;
576 0         0 my $iter = $self->iterator;
577 0         0 my $class = ref($self);
578 0         0 my $skipping = 1;
579              
580             return $class->new(sub {
581 0     0   0 while (1) {
582 0         0 my $item = $iter->();
583 0 0       0 return undef unless defined $item;
584              
585 0 0       0 if ($skipping) {
586 0 0       0 if (!$predicate->($item)) {
587 0         0 $skipping = 0;
588 0         0 return $item;
589             }
590             }
591             else {
592 0         0 return $item;
593             }
594             }
595 0         0 });
596             }
597              
598             ###############################################################################
599             # Ordering Methods
600             ###############################################################################
601              
602             # OrderBy - sort ascending (smart: numeric when both keys look numeric)
603             sub OrderBy {
604 3     3 1 10 my($self, $key_selector) = @_;
605 3         1059 my @items = $self->ToArray();
606 3         41 return JSON::LINQ::Ordered->_new_ordered(
607             [ @items ],
608             [{ sel => $key_selector, dir => 1, type => 'smart' }]
609             );
610             }
611              
612             # OrderByDescending - sort descending (smart comparison)
613             sub OrderByDescending {
614 2     2 1 6 my($self, $key_selector) = @_;
615 2         7 my @items = $self->ToArray();
616 2         22 return JSON::LINQ::Ordered->_new_ordered(
617             [ @items ],
618             [{ sel => $key_selector, dir => -1, type => 'smart' }]
619             );
620             }
621              
622             # OrderByStr - sort ascending by string comparison
623             sub OrderByStr {
624 7     7 1 10 my($self, $key_selector) = @_;
625 7         14 my @items = $self->ToArray();
626 7         35 return JSON::LINQ::Ordered->_new_ordered(
627             [ @items ],
628             [{ sel => $key_selector, dir => 1, type => 'str' }]
629             );
630             }
631              
632             # OrderByStrDescending - sort descending by string comparison
633             sub OrderByStrDescending {
634 0     0 1 0 my($self, $key_selector) = @_;
635 0         0 my @items = $self->ToArray();
636 0         0 return JSON::LINQ::Ordered->_new_ordered(
637             [ @items ],
638             [{ sel => $key_selector, dir => -1, type => 'str' }]
639             );
640             }
641              
642             # OrderByNum - sort ascending by numeric comparison
643             sub OrderByNum {
644 4     4 1 9 my($self, $key_selector) = @_;
645 4         14 my @items = $self->ToArray();
646 4         32 return JSON::LINQ::Ordered->_new_ordered(
647             [ @items ],
648             [{ sel => $key_selector, dir => 1, type => 'num' }]
649             );
650             }
651              
652             # OrderByNumDescending - sort descending by numeric comparison
653             sub OrderByNumDescending {
654 1     1 1 2 my($self, $key_selector) = @_;
655 1         2 my @items = $self->ToArray();
656 1         5 return JSON::LINQ::Ordered->_new_ordered(
657             [ @items ],
658             [{ sel => $key_selector, dir => -1, type => 'num' }]
659             );
660             }
661              
662             # Reverse - reverse order
663             sub Reverse {
664 1     1 1 2 my($self) = @_;
665 1         2 my @items = reverse $self->ToArray();
666 1         2 my $class = ref($self);
667 1         3 return $class->From([ @items ]);
668             }
669              
670             ###############################################################################
671             # Grouping Methods
672             ###############################################################################
673              
674             # GroupBy - group elements by key
675             sub GroupBy {
676 6     6 1 15 my($self, $key_selector, $element_selector) = @_;
677 6   66 19   45 $element_selector ||= sub { $_[0] };
  19         47  
678              
679 6         13 my %groups;
680             my @key_order;
681              
682             $self->ForEach(sub {
683 23     23   39 my $item = shift;
684 23         51 my $key = $key_selector->($item);
685 23 50       118 $key = '' unless defined $key;
686 23 100       49 unless (exists $groups{$key}) {
687 15         28 push @key_order, $key;
688             }
689 23         37 push @{$groups{$key}}, $element_selector->($item);
  23         56  
690 6         74 });
691              
692 6         35 my @result;
693 6         14 for my $key (@key_order) {
694             push @result, {
695             Key => $key,
696 15         51 Elements => $groups{$key},
697             };
698             }
699              
700 6         15 my $class = ref($self);
701 6         21 return $class->From([ @result ]);
702             }
703              
704             ###############################################################################
705             # Set Operations
706             ###############################################################################
707              
708             # Distinct - remove duplicates
709             sub Distinct {
710 5     5 1 11 my($self, $key_selector) = @_;
711 5         13 my $iter = $self->iterator;
712 5         10 my $class = ref($self);
713 5         8 my %seen;
714              
715             return $class->new(sub {
716 22     22   24 while (1) {
717 32         53 my $item = $iter->();
718 32 100       80 return undef unless defined $item;
719              
720 27 100       53 my $key = $key_selector ? $key_selector->($item) : _make_key($item);
721 27 50       40 $key = '' unless defined $key;
722              
723 27 100       68 unless ($seen{$key}++) {
724 17         33 return $item;
725             }
726             }
727 5         20 });
728             }
729              
730             # Internal helper for set operations - make key from item
731             sub _make_key {
732 40     40   45 my($item) = @_;
733              
734 40 50       57 return '' unless defined $item;
735              
736 40 50       62 if (ref($item) eq 'HASH') {
    50          
737 0         0 my @pairs = ();
738 0         0 for my $k (sort keys %$item) {
739 0 0       0 my $v = defined($item->{$k}) ? $item->{$k} : '';
740 0         0 push @pairs, "$k\x1F$v"; # \x1F = Unit Separator
741             }
742 0         0 return join("\x1E", @pairs); # \x1E = Record Separator
743             }
744             elsif (ref($item) eq 'ARRAY') {
745 0 0       0 return join("\x1E", map { defined($_) ? $_ : '' } @$item);
  0         0  
746             }
747             else {
748 40         49 return $item;
749             }
750             }
751              
752             # _from_snapshot - internal helper for GroupJoin.
753             sub _from_snapshot {
754 16     16   21 my($class_or_self, $aref) = @_;
755              
756 16   33     37 my $class = ref($class_or_self) || $class_or_self;
757              
758             my $iter_factory = sub {
759 32     32   26 my $i = 0;
760             return sub {
761 32 100       64 return undef if $i >= scalar(@$aref);
762 16         57 return $aref->[$i++];
763 32         88 };
764 16         43 };
765              
766 16         25 my $obj = bless {
767             iterator => $iter_factory->(),
768             _factory => $iter_factory,
769             }, $class;
770              
771 16         23 return $obj;
772             }
773              
774             # Union - set union with distinct
775             sub Union {
776 1     1 1 3 my($self, $second, $key_selector) = @_;
777              
778 1         26 return $self->Concat($second)->Distinct($key_selector);
779             }
780              
781             # Intersect - set intersection
782             sub Intersect {
783 1     1 1 3 my($self, $second, $key_selector) = @_;
784              
785 1         2 my %second_set = ();
786             $second->ForEach(sub {
787 4     4   5 my $item = shift;
788 4 50       5 my $key = $key_selector ? $key_selector->($item) : _make_key($item);
789 4         7 $second_set{$key} = $item;
790 1         5 });
791              
792 1         3 my $class = ref($self);
793 1         3 my $iter = $self->iterator;
794 1         1 my %seen = ();
795              
796             return $class->new(sub {
797 3     3   886 while (defined(my $item = $iter->())) {
798 4 50       11 my $key = $key_selector ? $key_selector->($item) : _make_key($item);
799              
800 4 50       11 next if $seen{$key}++;
801 4 100       10 return $item if exists $second_set{$key};
802             }
803 1         2 return undef;
804 1         9 });
805             }
806              
807             # Except - set difference
808             sub Except {
809 1     1 1 2 my($self, $second, $key_selector) = @_;
810              
811 1         2 my %second_set = ();
812             $second->ForEach(sub {
813 4     4   3 my $item = shift;
814 4 50       5 my $key = $key_selector ? $key_selector->($item) : _make_key($item);
815 4         5 $second_set{$key} = 1;
816 1         4 });
817              
818 1         4 my $class = ref($self);
819 1         1 my $iter = $self->iterator;
820 1         2 my %seen = ();
821              
822             return $class->new(sub {
823 3     3   4 while (defined(my $item = $iter->())) {
824 4 50       5 my $key = $key_selector ? $key_selector->($item) : _make_key($item);
825              
826 4 50       7 next if $seen{$key}++;
827 4 100       8 return $item unless exists $second_set{$key};
828             }
829 1         2 return undef;
830 1         3 });
831             }
832              
833             # Join - correlates elements of two sequences
834             sub Join {
835 2     2 1 7 my($self, $inner, $outer_key_selector, $inner_key_selector, $result_selector) = @_;
836              
837 2         3 my %inner_hash = ();
838             $inner->ForEach(sub {
839 8     8   10 my $item = shift;
840 8         15 my $key = $inner_key_selector->($item);
841 8 50       28 $key = _make_key($key) if ref($key);
842 8         11 push @{$inner_hash{$key}}, $item;
  8         24  
843 2         12 });
844              
845 2         10 my $class = ref($self);
846 2         5 my $iter = $self->iterator;
847 2         4 my @buffer = ();
848              
849             return $class->new(sub {
850 10     10   13 while (1) {
851 18 100       64 return shift @buffer if @buffer;
852              
853 10         16 my $outer_item = $iter->();
854 10 100       21 return undef unless defined $outer_item;
855              
856 8         17 my $key = $outer_key_selector->($outer_item);
857 8 50       27 $key = _make_key($key) if ref($key);
858              
859 8 100       18 if (exists $inner_hash{$key}) {
860 6         10 for my $inner_item (@{$inner_hash{$key}}) {
  6         11  
861 8         27 push @buffer, $result_selector->($outer_item, $inner_item);
862             }
863             }
864             }
865 2         10 });
866             }
867              
868             # GroupJoin - group join (LEFT OUTER JOIN-like operation)
869             sub GroupJoin {
870 4     4 1 7 my($self, $inner, $outer_key_selector, $inner_key_selector, $result_selector) = @_;
871 4         9 my $class = ref($self);
872 4         29 my $outer_iter = $self->iterator;
873              
874 4         6 my %inner_lookup = ();
875             $inner->ForEach(sub {
876 16     16   20 my $item = shift;
877 16         26 my $key = $inner_key_selector->($item);
878 16 50       62 $key = _make_key($key) if ref($key);
879 16 50       26 $key = '' unless defined $key;
880 16         19 push @{$inner_lookup{$key}}, $item;
  16         46  
881 4         16 });
882              
883             return $class->new(sub {
884 20     20   31 my $outer_item = $outer_iter->();
885 20 100       38 return undef unless defined $outer_item;
886              
887 16         27 my $key = $outer_key_selector->($outer_item);
888 16 50       46 $key = _make_key($key) if ref($key);
889 16 50       24 $key = '' unless defined $key;
890              
891 16 100       32 my $matched_inners = exists $inner_lookup{$key} ? $inner_lookup{$key} : [];
892              
893 16         25 my @snapshot = @$matched_inners;
894 16         32 my $inner_group = $class->_from_snapshot([ @snapshot ]);
895              
896 16         25 return $result_selector->($outer_item, $inner_group);
897 4         25 });
898             }
899              
900             ###############################################################################
901             # Quantifier Methods
902             ###############################################################################
903              
904             # All - test if all elements satisfy condition
905             sub All {
906 2     2 1 7 my($self, $predicate) = @_;
907 2         4 my $iter = $self->iterator;
908              
909 2         5 while (defined(my $item = $iter->())) {
910 6 100       10 return 0 unless $predicate->($item);
911             }
912 1         2 return 1;
913             }
914              
915             # Any - test if any element satisfies condition
916             sub Any {
917 6     6 1 7 my($self, $predicate) = @_;
918 6         6 my $iter = $self->iterator;
919              
920 6 100       8 if ($predicate) {
921 4         4 while (defined(my $item = $iter->())) {
922 11 100       20 return 1 if $predicate->($item);
923             }
924 2         5 return 0;
925             }
926             else {
927 2         3 my $item = $iter->();
928 2 100       6 return defined($item) ? 1 : 0;
929             }
930             }
931              
932             # Contains - check if sequence contains element
933             sub Contains {
934 2     2 1 4 my($self, $value, $comparer) = @_;
935              
936 2 50       3 if ($comparer) {
937 0     0   0 return $self->Any(sub { $comparer->($_[0], $value) });
  0         0  
938             }
939             else {
940             return $self->Any(sub {
941 5     5   4 my $item = $_[0];
942 5   33     28 return (!defined($item) && !defined($value)) ||
943             (defined($item) && defined($value) && $item eq $value);
944 2         4 });
945             }
946             }
947              
948             # SequenceEqual - compare two sequences for equality
949             sub SequenceEqual {
950 2     2 1 3 my($self, $second, $comparer) = @_;
951             $comparer ||= sub {
952 6     6   12 my($a, $b) = @_;
953 6   33     27 return (!defined($a) && !defined($b)) ||
954             (defined($a) && defined($b) && $a eq $b);
955 2   33     8 };
956              
957 2         3 my $iter1 = $self->iterator;
958 2         3 my $iter2 = $second->iterator;
959              
960 2         2 while (1) {
961 7         9 my $item1 = $iter1->();
962 7         7 my $item2 = $iter2->();
963              
964 7 50 66     12 return 1 if !defined($item1) && !defined($item2);
965 6 50 33     13 return 0 if !defined($item1) || !defined($item2);
966 6 100       7 return 0 unless $comparer->($item1, $item2);
967             }
968             }
969              
970             ###############################################################################
971             # Element Access Methods
972             ###############################################################################
973              
974             # First - get first element
975             sub First {
976 1     1 1 3 my($self, $predicate) = @_;
977 1         3 my $iter = $self->iterator;
978              
979 1 50       4 if ($predicate) {
980 1         4 while (defined(my $item = $iter->())) {
981 2 100       5 return $item if $predicate->($item);
982             }
983 0         0 die "No element satisfies the condition";
984             }
985             else {
986 0         0 my $item = $iter->();
987 0 0       0 return $item if defined $item;
988 0         0 die "Sequence contains no elements";
989             }
990             }
991              
992             # FirstOrDefault - get first element or default
993             sub FirstOrDefault {
994 0     0 1 0 my $self = shift;
995 0         0 my($predicate, $default);
996              
997 0 0       0 if (@_ >= 2) {
    0          
998 0         0 ($predicate, $default) = @_;
999             }
1000             elsif (@_ == 1) {
1001 0 0       0 if (ref($_[0]) eq 'CODE') {
1002 0         0 $predicate = $_[0];
1003             }
1004             else {
1005 0         0 $default = $_[0];
1006             }
1007             }
1008              
1009 0         0 my $result = eval { $self->First($predicate) };
  0         0  
1010 0 0       0 return $@ ? $default : $result;
1011             }
1012              
1013             # Last - get last element
1014             sub Last {
1015 1     1 1 4 my($self, $predicate) = @_;
1016 1         3 my @items = $self->ToArray();
1017              
1018 1 50       3 if ($predicate) {
1019 1         5 for (my $i = $#items; $i >= 0; $i--) {
1020 1 50       17 return $items[$i] if $predicate->($items[$i]);
1021             }
1022 0         0 die "No element satisfies the condition";
1023             }
1024             else {
1025 0 0       0 die "Sequence contains no elements" unless @items;
1026 0         0 return $items[-1];
1027             }
1028             }
1029              
1030             # LastOrDefault - return last element or default
1031             sub LastOrDefault {
1032 0     0 1 0 my $self = shift;
1033 0         0 my($predicate, $default);
1034              
1035 0 0       0 if (@_ >= 2) {
    0          
1036 0         0 ($predicate, $default) = @_;
1037             }
1038             elsif (@_ == 1) {
1039 0 0       0 if (ref($_[0]) eq 'CODE') {
1040 0         0 $predicate = $_[0];
1041             }
1042             else {
1043 0         0 $default = $_[0];
1044             }
1045             }
1046              
1047 0         0 my @items = $self->ToArray();
1048              
1049 0 0       0 if ($predicate) {
1050 0         0 for (my $i = $#items; $i >= 0; $i--) {
1051 0 0       0 return $items[$i] if $predicate->($items[$i]);
1052             }
1053 0         0 return $default;
1054             }
1055             else {
1056 0 0       0 return @items ? $items[-1] : $default;
1057             }
1058             }
1059              
1060             # Single - return the only element
1061             sub Single {
1062 0     0 1 0 my($self, $predicate) = @_;
1063 0         0 my $iter = $self->iterator;
1064 0         0 my $found;
1065 0         0 my $count = 0;
1066              
1067 0         0 while (defined(my $item = $iter->())) {
1068 0 0 0     0 next if $predicate && !$predicate->($item);
1069              
1070 0         0 $count++;
1071 0 0       0 if ($count > 1) {
1072 0         0 die "Sequence contains more than one element";
1073             }
1074 0         0 $found = $item;
1075             }
1076              
1077 0 0       0 die "Sequence contains no elements" if $count == 0;
1078 0         0 return $found;
1079             }
1080              
1081             # SingleOrDefault - return the only element or undef
1082             sub SingleOrDefault {
1083 0     0 1 0 my($self, $predicate) = @_;
1084 0         0 my $iter = $self->iterator;
1085 0         0 my $found;
1086 0         0 my $count = 0;
1087              
1088 0         0 while (defined(my $item = $iter->())) {
1089 0 0 0     0 next if $predicate && !$predicate->($item);
1090              
1091 0         0 $count++;
1092 0 0       0 if ($count > 1) {
1093 0         0 return undef;
1094             }
1095 0         0 $found = $item;
1096             }
1097              
1098 0 0       0 return $count == 1 ? $found : undef;
1099             }
1100              
1101             # ElementAt - return element at specified index
1102             sub ElementAt {
1103 0     0 1 0 my($self, $index) = @_;
1104 0 0       0 die "Index must be non-negative" if $index < 0;
1105              
1106 0         0 my $iter = $self->iterator;
1107 0         0 my $current = 0;
1108              
1109 0         0 while (defined(my $item = $iter->())) {
1110 0 0       0 return $item if $current == $index;
1111 0         0 $current++;
1112             }
1113              
1114 0         0 die "Index out of range";
1115             }
1116              
1117             # ElementAtOrDefault - return element at index or undef
1118             sub ElementAtOrDefault {
1119 0     0 1 0 my($self, $index) = @_;
1120 0 0       0 return undef if $index < 0;
1121              
1122 0         0 my $iter = $self->iterator;
1123 0         0 my $current = 0;
1124              
1125 0         0 while (defined(my $item = $iter->())) {
1126 0 0       0 return $item if $current == $index;
1127 0         0 $current++;
1128             }
1129              
1130 0         0 return undef;
1131             }
1132              
1133             ###############################################################################
1134             # Aggregation Methods
1135             ###############################################################################
1136              
1137             # Count - count elements
1138             sub Count {
1139 16     16 1 69 my($self, $predicate) = @_;
1140              
1141 16 100       30 if ($predicate) {
1142 1         4 return $self->Where($predicate)->Count();
1143             }
1144              
1145 15         19 my $count = 0;
1146 15         24 my $iter = $self->iterator;
1147 15         22 $count++ while defined $iter->();
1148 15         86 return $count;
1149             }
1150              
1151             # Sum - calculate sum
1152             sub Sum {
1153 8     8 1 39 my($self, $selector) = @_;
1154 8   66 5   57 $selector ||= sub { $_[0] };
  5         11  
1155              
1156 8         16 my $sum = 0;
1157             $self->ForEach(sub {
1158 17     17   45 $sum += $selector->(shift);
1159 8         41 });
1160 8         71 return $sum;
1161             }
1162              
1163             # Min - find minimum
1164             sub Min {
1165 1     1 1 3 my($self, $selector) = @_;
1166 1   33 0   3 $selector ||= sub { $_[0] };
  0         0  
1167              
1168 1         2 my $min;
1169             $self->ForEach(sub {
1170 5     5   10 my $val = $selector->(shift);
1171 5 100 100     30 $min = $val if !defined($min) || $val < $min;
1172 1         5 });
1173 1         5 return $min;
1174             }
1175              
1176             # Max - find maximum
1177             sub Max {
1178 1     1 1 3 my($self, $selector) = @_;
1179 1   33 0   6 $selector ||= sub { $_[0] };
  0         0  
1180              
1181 1         2 my $max;
1182             $self->ForEach(sub {
1183 5     5   11 my $val = $selector->(shift);
1184 5 100 100     39 $max = $val if !defined($max) || $val > $max;
1185 1         6 });
1186 1         6 return $max;
1187             }
1188              
1189             # Average - calculate average
1190             sub Average {
1191 1     1 1 3 my($self, $selector) = @_;
1192 1   33 0   24 $selector ||= sub { $_[0] };
  0         0  
1193              
1194 1         3 my $sum = 0;
1195 1         2 my $count = 0;
1196             $self->ForEach(sub {
1197 5     5   9 $sum += $selector->(shift);
1198 5         20 $count++;
1199 1         6 });
1200              
1201 1 50       6 die "Sequence contains no elements" if $count == 0;
1202 1         4 return $sum / $count;
1203             }
1204              
1205             # AverageOrDefault - calculate average or return undef if empty
1206             sub AverageOrDefault {
1207 1     1 1 3 my($self, $selector) = @_;
1208 1   33 0   4 $selector ||= sub { $_[0] };
  0         0  
1209              
1210 1         1 my $sum = 0;
1211 1         1 my $count = 0;
1212             $self->ForEach(sub {
1213 0     0   0 $sum += $selector->(shift);
1214 0         0 $count++;
1215 1         4 });
1216              
1217 1 50       5 return undef if $count == 0;
1218 0         0 return $sum / $count;
1219             }
1220              
1221             # Aggregate - apply accumulator function over sequence
1222             sub Aggregate {
1223 3     3 1 5 my($self, @args) = @_;
1224              
1225 3         3 my($seed, $func, $result_selector);
1226              
1227 3 100       8 if (@args == 1) {
    100          
    50          
1228 1         1 $func = $args[0];
1229 1         2 my $iter = $self->iterator;
1230 1         2 $seed = $iter->();
1231 1 50       2 die "Sequence contains no elements" unless defined $seed;
1232              
1233 1         2 while (defined(my $item = $iter->())) {
1234 3         4 $seed = $func->($seed, $item);
1235             }
1236             }
1237             elsif (@args == 2) {
1238 1         2 ($seed, $func) = @args;
1239             $self->ForEach(sub {
1240 3     3   4 $seed = $func->($seed, shift);
1241 1         2 });
1242             }
1243             elsif (@args == 3) {
1244 1         1 ($seed, $func, $result_selector) = @args;
1245             $self->ForEach(sub {
1246 3     3   5 $seed = $func->($seed, shift);
1247 1         7 });
1248             }
1249             else {
1250 0         0 die "Invalid number of arguments for Aggregate";
1251             }
1252              
1253 3 100       9 return $result_selector ? $result_selector->($seed) : $seed;
1254             }
1255              
1256             ###############################################################################
1257             # Conversion Methods
1258             ###############################################################################
1259              
1260             # ToArray - convert to array
1261             sub ToArray {
1262 88     88 1 192 my($self) = @_;
1263 88         127 my @result;
1264 88         193 my $iter = $self->iterator;
1265              
1266 88         296 while (defined(my $item = $iter->())) {
1267 315         916 push @result, $item;
1268             }
1269 88         392 return @result;
1270             }
1271              
1272             # ToList - convert to array reference
1273             sub ToList {
1274 0     0 1 0 my($self) = @_;
1275 0         0 return [$self->ToArray()];
1276             }
1277              
1278             # ToDictionary - convert sequence to hash reference
1279             sub ToDictionary {
1280 1     1 1 3 my($self, $key_selector, $value_selector) = @_;
1281              
1282 1   33 0   5 $value_selector ||= sub { $_[0] };
  0         0  
1283              
1284 1         2 my %dictionary = ();
1285              
1286             $self->ForEach(sub {
1287 4     4   7 my $item = shift;
1288 4         11 my $key = $key_selector->($item);
1289 4         1213 my $value = $value_selector->($item);
1290              
1291 4 50       24 $key = '' unless defined $key;
1292 4         14 $dictionary{$key} = $value;
1293 1         7 });
1294              
1295 1         12 return { %dictionary };
1296             }
1297              
1298             # ToLookup - convert sequence to hash of arrays
1299             sub ToLookup {
1300 2     2 1 6 my($self, $key_selector, $value_selector) = @_;
1301              
1302 2   33 0   8 $value_selector ||= sub { $_[0] };
  0         0  
1303              
1304 2         4 my %lookup = ();
1305              
1306             $self->ForEach(sub {
1307 8     8   11 my $item = shift;
1308 8         35 my $key = $key_selector->($item);
1309 8         27 my $value = $value_selector->($item);
1310              
1311 8 50       23 $key = '' unless defined $key;
1312 8         7 push @{$lookup{$key}}, $value;
  8         21  
1313 2         11 });
1314              
1315 2         15 return { %lookup };
1316             }
1317              
1318             # DefaultIfEmpty - return default value if empty
1319             sub DefaultIfEmpty {
1320 2     2 1 3 my($self, $default_value) = @_;
1321 2         3 my $has_default_arg = @_ > 1;
1322 2 50       3 if (!$has_default_arg) {
1323 0         0 $default_value = undef;
1324             }
1325              
1326 2         2 my $class = ref($self);
1327 2         4 my $iter = $self->iterator;
1328 2         2 my $has_elements = 0;
1329 2         2 my $returned_default = 0;
1330              
1331             return $class->new(sub {
1332 6     6   6 my $item = $iter->();
1333 6 100       7 if (defined $item) {
1334 3         3 $has_elements = 1;
1335 3         4 return $item;
1336             }
1337              
1338 3 100 100     7 if (!$has_elements && !$returned_default) {
1339 1         1 $returned_default = 1;
1340 1         2 return $default_value;
1341             }
1342              
1343 2         3 return undef;
1344 2         5 });
1345             }
1346              
1347             # ToJSON - write sequence as a JSON array file
1348             # Each element is encoded as JSON; the result is a JSON array.
1349             sub ToJSON {
1350 1     1 1 3 my($self, $filename) = @_;
1351              
1352 1         2 my $fh;
1353 1 50       5 if ($] >= 5.006) {
1354 1 50       91 eval q{ open($fh, '>', $filename) } or die "Cannot open '$filename': $!";
1355             }
1356             else {
1357 0         0 $fh = \do { local *_ };
  0         0  
1358 0 0       0 open($fh, "> $filename") or die "Cannot open '$filename': $!";
1359             }
1360 1         9 binmode $fh; # Write raw UTF-8 bytes
1361              
1362 1         5 print $fh "[\n";
1363 1         2 my $first = 1;
1364             $self->ForEach(sub {
1365 2     2   4 my $record = shift;
1366 2 100       7 print $fh ",\n" unless $first;
1367 2         5 $first = 0;
1368 2         5 print $fh _json_encode($record);
1369 1         15 });
1370 1         7 print $fh "\n]\n";
1371              
1372 1         66 close $fh;
1373 1         12 return 1;
1374             }
1375              
1376             # ToJSONL - write sequence as a JSONL (JSON Lines) file
1377             # Each element is encoded as one line of JSON.
1378             # This is streaming-friendly and memory-efficient.
1379             sub ToJSONL {
1380 1     1 1 3 my($self, $filename) = @_;
1381              
1382 1         3 my $fh;
1383 1 50       6 if ($] >= 5.006) {
1384 1 50       181 eval q{ open($fh, '>', $filename) } or die "Cannot open '$filename': $!";
1385             }
1386             else {
1387 0         0 $fh = \do { local *_ };
  0         0  
1388 0 0       0 open($fh, "> $filename") or die "Cannot open '$filename': $!";
1389             }
1390 1         8 binmode $fh; # Write raw UTF-8 bytes
1391              
1392             $self->ForEach(sub {
1393 2     2   4 my $record = shift;
1394 2         6 print $fh _json_encode($record), "\n";
1395 1         9 });
1396              
1397 1         59 close $fh;
1398 1         11 return 1;
1399             }
1400              
1401             ###############################################################################
1402             # Utility Methods
1403             ###############################################################################
1404              
1405             # ForEach - execute action for each element
1406             sub ForEach {
1407 33     33 1 61 my($self, $action) = @_;
1408 33         75 my $iter = $self->iterator;
1409              
1410 33         69 while (defined(my $item = $iter->())) {
1411 109         181 $action->($item);
1412             }
1413 33         65 return;
1414             }
1415              
1416             1;
1417              
1418             ######################################################################
1419             #
1420             # JSON::LINQ::Ordered - Ordered query supporting ThenBy/ThenByDescending
1421             #
1422             # Returned by OrderBy* methods. Inherits all JSON::LINQ methods via @ISA.
1423             # Stability guarantee: Schwartzian-Transform stable sort, all Perl versions.
1424             #
1425             ######################################################################
1426              
1427             package JSON::LINQ::Ordered;
1428              
1429             @JSON::LINQ::Ordered::ISA = ('JSON::LINQ');
1430              
1431             sub _new_ordered {
1432 23     23   44 my($class, $items, $specs) = @_;
1433             return bless {
1434             _items => $items,
1435             _specs => $specs,
1436             _factory => sub {
1437 18     18   40 my @sorted = _perform_sort($items, $specs);
1438 18         28 my $i = 0;
1439 18 100       103 return sub { $i < scalar(@sorted) ? $sorted[$i++] : undef };
  98         212  
1440             },
1441 23         214 }, $class;
1442             }
1443              
1444             sub _perform_sort {
1445 18     18   37 my($items, $specs) = @_;
1446              
1447             my @decorated = map {
1448 84         118 my $idx = $_;
1449 84         112 my $item = $items->[$idx];
1450 84         113 my @keys = map { _extract_key($_->{sel}->($item), $_->{type}) } @{$specs};
  114         224  
  84         126  
1451 84         237 [$idx, [ @keys ], $item]
1452 18         32 } 0 .. $#{$items};
  18         47  
1453              
1454             my @sorted_dec = sort {
1455 18         76 my $r = 0;
  125         167  
1456 125         176 for my $i (0 .. $#{$specs}) {
  125         228  
1457 146         295 my $cmp = _compare_keys($a->[1][$i], $b->[1][$i], $specs->[$i]{type});
1458 146 100       274 if ($specs->[$i]{dir} < 0) { $cmp = -$cmp }
  26         32  
1459 146 100       253 if ($cmp != 0) { $r = $cmp; last }
  118         154  
  118         160  
1460             }
1461 125 100       300 $r != 0 ? $r : ($a->[0] <=> $b->[0]);
1462             } @decorated;
1463              
1464 18         1063 return map { $_->[2] } @sorted_dec;
  84         200  
1465             }
1466              
1467             sub _extract_key {
1468 114     114   378 my($val, $type) = @_;
1469 114 50       195 $val = '' unless defined $val;
1470 114 100       212 if ($type eq 'num') {
    100          
1471 32 50 33     120 return defined($val) && length($val) ? $val + 0 : 0;
1472             }
1473             elsif ($type eq 'str') {
1474 56         118 return "$val";
1475             }
1476             else {
1477 26         42 my $t = $val;
1478 26         95 $t =~ s/^\s+|\s+$//g;
1479 26 100       123 if ($t =~ /^[+-]?(?:\d+\.?\d*|\d*\.\d+)(?:[eE][+-]?\d+)?$/) {
1480 16         57 return [0, $t + 0];
1481             }
1482             else {
1483 10         47 return [1, "$val"];
1484             }
1485             }
1486             }
1487              
1488             sub _compare_keys {
1489 146     146   246 my($ka, $kb, $type) = @_;
1490 146 100       259 if ($type eq 'num') {
    100          
1491 39         63 return $ka <=> $kb;
1492             }
1493             elsif ($type eq 'str') {
1494 69         107 return $ka cmp $kb;
1495             }
1496             else {
1497 38         92 my $fa = $ka->[0]; my $va = $ka->[1];
  38         90  
1498 38         60 my $fb = $kb->[0]; my $vb = $kb->[1];
  38         61  
1499 38 100 66     194 if ($fa == 0 && $fb == 0) { return $va <=> $vb }
  23 50 33     55  
1500 15         82 elsif ($fa == 1 && $fb == 1) { return $va cmp $vb }
1501 0         0 else { return $fa <=> $fb }
1502             }
1503             }
1504              
1505             sub _thenby {
1506 6     6   10 my($self, $key_selector, $dir, $type) = @_;
1507 6         8 my @new_specs = (@{$self->{_specs}}, { sel => $key_selector, dir => $dir, type => $type });
  6         18  
1508 6         14 return JSON::LINQ::Ordered->_new_ordered($self->{_items}, [ @new_specs ]);
1509             }
1510              
1511 0     0   0 sub ThenBy { my($s, $k)=@_; $s->_thenby($k, 1, 'smart') }
  0         0  
1512 0     0   0 sub ThenByDescending { my($s, $k)=@_; $s->_thenby($k, -1, 'smart') }
  0         0  
1513 4     4   14 sub ThenByStr { my($s, $k)=@_; $s->_thenby($k, 1, 'str') }
  4         9  
1514 0     0   0 sub ThenByStrDescending { my($s, $k)=@_; $s->_thenby($k, -1, 'str') }
  0         0  
1515 0     0   0 sub ThenByNum { my($s, $k)=@_; $s->_thenby($k, 1, 'num') }
  0         0  
1516 2     2   19 sub ThenByNumDescending { my($s, $k)=@_; $s->_thenby($k, -1, 'num') }
  2         4  
1517              
1518             1;
1519              
1520             =encoding utf-8
1521              
1522             =head1 NAME
1523              
1524             JSON::LINQ - LINQ-style query interface for JSON and JSONL files
1525              
1526             =head1 VERSION
1527              
1528             Version 1.00
1529              
1530             =head1 SYNOPSIS
1531              
1532             use JSON::LINQ;
1533              
1534             # Read JSON file (array of objects) and query
1535             my @results = JSON::LINQ->FromJSON("users.json")
1536             ->Where(sub { $_[0]{age} >= 18 })
1537             ->Select(sub { $_[0]{name} })
1538             ->Distinct()
1539             ->ToArray();
1540              
1541             # Read JSONL (JSON Lines) file - one JSON object per line
1542             my @errors = JSON::LINQ->FromJSONL("events.jsonl")
1543             ->Where(sub { $_[0]{level} eq 'ERROR' })
1544             ->ToArray();
1545              
1546             # DSL syntax for simple filtering
1547             my @active = JSON::LINQ->FromJSON("users.json")
1548             ->Where(status => 'active')
1549             ->ToArray();
1550              
1551             # Grouping and aggregation
1552             my @stats = JSON::LINQ->FromJSON("orders.json")
1553             ->GroupBy(sub { $_[0]{category} })
1554             ->Select(sub {
1555             my $g = shift;
1556             return {
1557             Category => $g->{Key},
1558             Count => scalar(@{$g->{Elements}}),
1559             Total => JSON::LINQ->From($g->{Elements})
1560             ->Sum(sub { $_[0]{amount} }),
1561             };
1562             })
1563             ->OrderByDescending(sub { $_[0]{Total} })
1564             ->ToArray();
1565              
1566             # Write results back as JSON or JSONL
1567             JSON::LINQ->From(\@results)->ToJSON("output.json");
1568             JSON::LINQ->From(\@results)->ToJSONL("output.jsonl");
1569              
1570             # Boolean values
1571             my $rec = { active => JSON::LINQ::true, count => 0 };
1572             JSON::LINQ->From([$rec])->ToJSON("output.json");
1573             # ToJSON encodes as: {"active":true,"count":0}
1574              
1575             =head1 TABLE OF CONTENTS
1576              
1577             =over 4
1578              
1579             =item * L
1580              
1581             =item * L -- eg/ samples and doc/ cheat sheets
1582              
1583             =item * L -- Complete method reference (63 methods)
1584              
1585             =item * L -- Practical examples
1586              
1587             =item * L -- Lazy evaluation, method chaining, DSL
1588              
1589             =item * L -- Iterator design, execution flow
1590              
1591             =item * L -- Perl 5.005+ support, pure Perl
1592              
1593             =item * L -- Error messages
1594              
1595             =item * L
1596              
1597             =item * L
1598              
1599             =back
1600              
1601             =head1 DESCRIPTION
1602              
1603             JSON::LINQ provides a LINQ-style query interface for JSON and JSONL
1604             (JSON Lines) files. It is the JSON counterpart of L, sharing
1605             the same 60-method LINQ API and adding three JSON-specific I/O methods.
1606              
1607             Key features:
1608              
1609             =over 4
1610              
1611             =item * B - O(1) memory for JSONL streaming; JSON arrays
1612             are loaded once then iterated lazily
1613              
1614             =item * B - Fluent, readable query composition
1615              
1616             =item * B - Simple key-value filtering
1617              
1618             =item * B<63 LINQ methods> - 60 from LTSV::LINQ + FromJSON, FromJSONL,
1619             FromJSONString, ToJSON, ToJSONL
1620              
1621             =item * B - No XS dependencies
1622              
1623             =item * B - Works on ancient and modern Perl
1624              
1625             =item * B - No CPAN JSON module required
1626              
1627             =back
1628              
1629             =head2 Supported Data Sources
1630              
1631             =over 4
1632              
1633             =item * B - JSON file containing a top-level array or object
1634              
1635             =item * B - JSONL file (one JSON value per line)
1636              
1637             =item * B - JSON string (array or object)
1638              
1639             =item * B - In-memory Perl array
1640              
1641             =item * B - Integer sequence
1642              
1643             =item * B - Empty sequence
1644              
1645             =item * B - Repeated element
1646              
1647             =back
1648              
1649             =head2 What is JSONL?
1650              
1651             JSONL (JSON Lines, also known as ndjson - newline-delimited JSON) is a
1652             text format where each line is a valid JSON value (typically an object).
1653             It is particularly suited for log files and streaming data because:
1654              
1655             =over 4
1656              
1657             =item * One record per line enables streaming with O(1) memory usage
1658              
1659             =item * Compatible with standard Unix tools (grep, sed, awk)
1660              
1661             =item * Easily appendable without rewriting the whole file
1662              
1663             =item * Each line is independently parseable
1664              
1665             =back
1666              
1667             B
1668              
1669             {"time":"2026-04-20T10:00:00","host":"192.0.2.1","status":200,"url":"/"}
1670             {"time":"2026-04-20T10:00:01","host":"192.0.2.2","status":404,"url":"/missing"}
1671              
1672             C reads these files lazily (one line at a time), matching the
1673             memory efficiency of C's C.
1674              
1675             =head2 What is LINQ?
1676              
1677             LINQ (Language Integrated Query) is the Microsoft .NET query API.
1678             This module brings the same 60-method LINQ interface to JSON data in Perl.
1679             See L for a detailed description of the LINQ design philosophy.
1680              
1681             =head1 INCLUDED DOCUMENTATION
1682              
1683             The C directory contains sample programs:
1684              
1685             eg/01_json_query.pl FromJSON/Where/Select/OrderByDescending/Distinct/ToLookup
1686             eg/02_jsonl_query.pl FromJSONL streaming, GroupBy, aggregation
1687             eg/03_grouping.pl GroupBy, ToLookup, GroupJoin, SelectMany
1688             eg/04_sorting.pl OrderBy/ThenBy multi-key sort
1689              
1690             The C directory contains JSON::LINQ cheat sheets in 21 languages:
1691              
1692             doc/json_linq_cheatsheet.EN.txt English
1693             doc/json_linq_cheatsheet.JA.txt Japanese
1694             doc/json_linq_cheatsheet.ZH.txt Chinese (Simplified)
1695             doc/json_linq_cheatsheet.TW.txt Chinese (Traditional)
1696             doc/json_linq_cheatsheet.KO.txt Korean
1697             doc/json_linq_cheatsheet.FR.txt French
1698             doc/json_linq_cheatsheet.ID.txt Indonesian
1699             doc/json_linq_cheatsheet.VI.txt Vietnamese
1700             doc/json_linq_cheatsheet.TH.txt Thai
1701             doc/json_linq_cheatsheet.HI.txt Hindi
1702             doc/json_linq_cheatsheet.BN.txt Bengali
1703             doc/json_linq_cheatsheet.TR.txt Turkish
1704             doc/json_linq_cheatsheet.MY.txt Malay
1705             doc/json_linq_cheatsheet.TL.txt Filipino
1706             doc/json_linq_cheatsheet.KM.txt Khmer
1707             doc/json_linq_cheatsheet.MN.txt Mongolian
1708             doc/json_linq_cheatsheet.NE.txt Nepali
1709             doc/json_linq_cheatsheet.SI.txt Sinhala
1710             doc/json_linq_cheatsheet.UR.txt Urdu
1711             doc/json_linq_cheatsheet.UZ.txt Uzbek
1712             doc/json_linq_cheatsheet.BM.txt Burmese
1713              
1714             =head1 METHODS
1715              
1716             =head2 Complete Method Reference
1717              
1718             This module implements 63 LINQ methods organized into 15 categories.
1719             In addition, C and C boolean accessor functions are provided.
1720              
1721             =over 4
1722              
1723             =item * B: From, FromJSON, FromJSONL, FromJSONString, Range, Empty, Repeat
1724              
1725             =item * B: Where (with DSL)
1726              
1727             =item * B: Select, SelectMany
1728              
1729             =item * B: Concat, Zip
1730              
1731             =item * B: Take, Skip, TakeWhile, SkipWhile
1732              
1733             =item * B: OrderBy, OrderByDescending, OrderByStr, OrderByStrDescending, OrderByNum, OrderByNumDescending, Reverse, ThenBy, ThenByDescending, ThenByStr, ThenByStrDescending, ThenByNum, ThenByNumDescending
1734              
1735             =item * B: GroupBy
1736              
1737             =item * B: Distinct, Union, Intersect, Except
1738              
1739             =item * B: Join, GroupJoin
1740              
1741             =item * B: All, Any, Contains
1742              
1743             =item * B: SequenceEqual
1744              
1745             =item * B: First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAt, ElementAtOrDefault
1746              
1747             =item * B: Count, Sum, Min, Max, Average, AverageOrDefault, Aggregate
1748              
1749             =item * B: ToArray, ToList, ToDictionary, ToLookup, ToJSON, ToJSONL, DefaultIfEmpty
1750              
1751             =item * B: ForEach
1752              
1753             =back
1754              
1755             =head2 JSON-Specific Data Source Methods
1756              
1757             =over 4
1758              
1759             =item B
1760              
1761             Read a JSON file containing a top-level array of values. Each element of
1762             the array becomes one item in the sequence.
1763              
1764             my $q = JSON::LINQ->FromJSON("users.json");
1765              
1766             If the file contains a single JSON object (not an array), it is treated
1767             as a one-element sequence.
1768              
1769             B
1770              
1771             [
1772             {"name": "Alice", "age": 30},
1773             {"name": "Bob", "age": 25}
1774             ]
1775              
1776             The entire file is read into memory and parsed once. For large files,
1777             consider JSONL format with C for streaming access.
1778              
1779             =item B
1780              
1781             Read a JSONL (JSON Lines) file. Each non-empty line is parsed as a
1782             separate JSON value. Empty lines and lines starting with C<#> are skipped.
1783              
1784             my $q = JSON::LINQ->FromJSONL("events.jsonl");
1785              
1786             B
1787              
1788             {"event":"login","user":"alice","ts":1713600000}
1789             {"event":"purchase","user":"alice","ts":1713600060,"amount":29.99}
1790             {"event":"logout","user":"alice","ts":1713600120}
1791              
1792             C reads lazily (one line at a time), providing O(1) memory
1793             usage for arbitrarily large files.
1794              
1795             Invalid JSON lines produce a warning and are skipped rather than
1796             aborting the entire sequence.
1797              
1798             =item B
1799              
1800             Create a query from a JSON string. Accepts a JSON array (each element
1801             becomes one sequence item) or a JSON object (single-element sequence).
1802              
1803             my $q = JSON::LINQ->FromJSONString('[{"id":1},{"id":2}]');
1804             my $q = JSON::LINQ->FromJSONString('{"id":1,"name":"Alice"}');
1805              
1806             =back
1807              
1808             =head2 JSON-Specific Conversion Methods
1809              
1810             =over 4
1811              
1812             =item B
1813              
1814             Write the sequence as a JSON file containing a JSON array. Each element
1815             is encoded as JSON. The output is a valid JSON array.
1816              
1817             $query->ToJSON("output.json");
1818              
1819             B
1820              
1821             [
1822             {"age":30,"name":"Alice"},
1823             {"age":25,"name":"Bob"}
1824             ]
1825              
1826             Hash keys are sorted alphabetically for deterministic output.
1827              
1828             =item B
1829              
1830             Write the sequence as a JSONL file. Each element is written as one line
1831             of JSON. This is the streaming counterpart of C.
1832              
1833             $query->ToJSONL("output.jsonl");
1834              
1835             B
1836              
1837             {"age":30,"name":"Alice"}
1838             {"age":25,"name":"Bob"}
1839              
1840             =back
1841              
1842             =head2 Boolean Values
1843              
1844             JSON::LINQ provides boolean singleton objects compatible with JSON encoding:
1845              
1846             JSON::LINQ::true # stringifies as "true", numifies as 1
1847             JSON::LINQ::false # stringifies as "false", numifies as 0
1848              
1849             Use these when creating data structures that will be serialised to JSON:
1850              
1851             my $rec = { active => JSON::LINQ::true, count => 0 };
1852             # ToJSON encodes as: {"active":true,"count":0}
1853              
1854             When C or C decode a JSON C or C,
1855             the result is a C object that behaves as 1 or 0
1856             in numeric and boolean context.
1857              
1858             =head2 All Other Methods
1859              
1860             All 60 LINQ methods from L are available unchanged.
1861             Please refer to L for complete documentation of:
1862              
1863             Where, Select, SelectMany, Concat, Zip, Take, Skip, TakeWhile,
1864             SkipWhile, OrderBy, OrderByDescending, OrderByStr, OrderByStrDescending,
1865             OrderByNum, OrderByNumDescending, Reverse, ThenBy, ThenByDescending,
1866             ThenByStr, ThenByStrDescending, ThenByNum, ThenByNumDescending, GroupBy,
1867             Distinct, Union, Intersect, Except, Join, GroupJoin, All, Any, Contains,
1868             SequenceEqual, First, FirstOrDefault, Last, LastOrDefault, Single,
1869             SingleOrDefault, ElementAt, ElementAtOrDefault, Count, Sum, Min, Max,
1870             Average, AverageOrDefault, Aggregate, ToArray, ToList, ToDictionary,
1871             ToLookup, DefaultIfEmpty, ForEach.
1872              
1873             =head1 EXAMPLES
1874              
1875             =head2 Basic JSON File Query
1876              
1877             use JSON::LINQ;
1878              
1879             # users.json: [{"name":"Alice","age":30}, {"name":"Bob","age":25}, ...]
1880             my @adults = JSON::LINQ->FromJSON("users.json")
1881             ->Where(sub { $_[0]{age} >= 18 })
1882             ->OrderBy(sub { $_[0]{name} })
1883             ->ToArray();
1884              
1885             =head2 JSONL Streaming
1886              
1887             # events.jsonl: one JSON object per line
1888             my $error_count = JSON::LINQ->FromJSONL("events.jsonl")
1889             ->Count(sub { $_[0]{level} eq 'ERROR' });
1890              
1891             JSON::LINQ->FromJSONL("events.jsonl")
1892             ->Where(sub { $_[0]{level} eq 'ERROR' })
1893             ->ForEach(sub { print $_[0]{message}, "\n" });
1894              
1895             =head2 Aggregation
1896              
1897             my $avg = JSON::LINQ->FromJSON("orders.json")
1898             ->Where(sub { $_[0]{status} eq 'completed' })
1899             ->Average(sub { $_[0]{amount} });
1900              
1901             printf "Average order: %.2f\n", $avg;
1902              
1903             =head2 Grouping
1904              
1905             my @by_category = JSON::LINQ->FromJSON("products.json")
1906             ->GroupBy(sub { $_[0]{category} })
1907             ->Select(sub {
1908             my $g = shift;
1909             {
1910             Category => $g->{Key},
1911             Count => scalar(@{$g->{Elements}}),
1912             MaxPrice => JSON::LINQ->From($g->{Elements})
1913             ->Max(sub { $_[0]{price} }),
1914             }
1915             })
1916             ->OrderByDescending(sub { $_[0]{Count} })
1917             ->ToArray();
1918              
1919             =head2 Transform and Write
1920              
1921             # Read JSON, transform, write back as JSONL
1922             JSON::LINQ->FromJSON("input.json")
1923             ->Select(sub {
1924             my $r = shift;
1925             return { %$r, processed => JSON::LINQ::true };
1926             })
1927             ->ToJSONL("output.jsonl");
1928              
1929             =head2 In-Memory Array Query
1930              
1931             my @data = (
1932             {name => 'Alice', score => 95},
1933             {name => 'Bob', score => 72},
1934             {name => 'Carol', score => 88},
1935             );
1936              
1937             my @top = JSON::LINQ->From(\@data)
1938             ->Where(sub { $_[0]{score} >= 80 })
1939             ->OrderByDescending(sub { $_[0]{score} })
1940             ->ToArray();
1941              
1942             =head1 FEATURES
1943              
1944             =head2 Lazy Evaluation
1945              
1946             C reads one line at a time. Combined with C and C,
1947             only the needed records are ever in memory simultaneously.
1948              
1949             C reads the whole file once but then iterates the array lazily.
1950              
1951             =head2 Built-in JSON Parser
1952              
1953             JSON::LINQ contains its own JSON encoder/decoder (derived from mb::JSON 0.06).
1954             No CPAN JSON module is required. The parser handles:
1955              
1956             =over 4
1957              
1958             =item * UTF-8 multibyte strings (output as-is, not \uXXXX-escaped)
1959              
1960             =item * C<\uXXXX> escape sequences on input (converted to UTF-8)
1961              
1962             =item * All JSON types: object, array, string, number, true, false, null
1963              
1964             =item * Nested structures of arbitrary depth
1965              
1966             =back
1967              
1968             =head1 ARCHITECTURE
1969              
1970             =head2 Relationship to LTSV::LINQ
1971              
1972             JSON::LINQ and LTSV::LINQ are parallel modules sharing the same LINQ API.
1973              
1974             LTSV::LINQ - LINQ for LTSV (Labeled Tab-Separated Values) files
1975             JSON::LINQ - LINQ for JSON and JSONL files
1976              
1977             Both provide the same 60 LINQ methods. JSON::LINQ adds:
1978              
1979             FromJSON($file) - read JSON array file
1980             FromJSONL($file) - read JSONL file (streaming)
1981             FromJSONString($json) - read JSON string
1982             ToJSON($file) - write JSON array file
1983             ToJSONL($file) - write JSONL file
1984              
1985             The internal iterator architecture is identical: each operator returns a
1986             new query object wrapping a closure.
1987              
1988             =head2 Memory Characteristics
1989              
1990             FromJSONL - O(1) per record: one line at a time
1991             FromJSON - O(n): entire file loaded once, then lazy iteration
1992             ToJSON - O(n): entire sequence collected for array output
1993             ToJSONL - O(1) per record: streaming write
1994              
1995             =head1 COMPATIBILITY
1996              
1997             =head2 Perl Version Support
1998              
1999             Compatible with B. See L for the
2000             full compatibility rationale (Universal Consensus 1998 / Perl 5.005_03).
2001              
2002             =head2 Pure Perl Implementation
2003              
2004             No XS dependencies. No CPAN module dependencies. Works on any Perl
2005             installation with only the standard core.
2006              
2007             =head2 JSON Limitations
2008              
2009             The built-in parser has the same limitations as mb::JSON 0.06:
2010              
2011             =over 4
2012              
2013             =item * Surrogate pairs (C<\uD800>-C<\uDFFF>) are not supported
2014              
2015             =item * Circular references in encoding cause infinite recursion
2016              
2017             =item * Non-ARRAY/HASH references are stringified
2018              
2019             =back
2020              
2021             =head1 DIAGNOSTICS
2022              
2023             =over 4
2024              
2025             =item C
2026              
2027             The file exists but does not contain valid JSON.
2028              
2029             =item C
2030              
2031             The file contains valid JSON but the top-level value is a string, number,
2032             or boolean, not an array or object.
2033              
2034             =item C
2035              
2036             A line in a JSONL file could not be parsed. The line is skipped with a
2037             warning; processing continues.
2038              
2039             =item C
2040              
2041             Internal JSON parsing error. The message includes the specific unexpected
2042             token or an indication of where parsing stopped.
2043              
2044             =item C
2045              
2046             The file exists but its content is not valid JSON.
2047              
2048             =item C
2049              
2050             The supplied JSON string is not valid JSON.
2051              
2052             =item C
2053              
2054             The JSON array was not properly terminated or separated.
2055              
2056             =item C
2057              
2058             A JSON object was not properly terminated or separated.
2059              
2060             =item C
2061              
2062             The colon separator was missing after a JSON object key.
2063              
2064             =item C
2065              
2066             A JSON object key was not a quoted string.
2067              
2068             =item C
2069              
2070             Extra text was found after a successfully parsed top-level JSON value.
2071              
2072             =item C
2073              
2074             The JSON text ended before a complete value was parsed.
2075              
2076             =item C
2077              
2078             An unrecognised token was encountered while parsing JSON.
2079              
2080             =item C
2081              
2082             A JSON string was not closed with a double-quote.
2083              
2084             =item C
2085              
2086             Thrown by C or C when the specified file cannot be opened.
2087              
2088             =item C
2089              
2090             Thrown by C or C when the output file cannot be opened.
2091              
2092             =item C
2093              
2094             Thrown by C when the argument is not an array reference.
2095              
2096             =item C
2097              
2098             Thrown by C when the supplied index is less than zero.
2099              
2100             =item C
2101              
2102             Thrown by C when the index is beyond the end of the sequence.
2103             Use C to avoid this error.
2104              
2105             =item C
2106              
2107             Thrown by C when called with an argument count other than 1, 2, or 3.
2108              
2109             =item C
2110              
2111             Thrown by C, C, C, C (no-seed form), and
2112             C when the sequence is empty or no element satisfies the predicate.
2113              
2114             =item C
2115              
2116             Thrown by C when more than one element (or matching element) is found.
2117              
2118             =item C
2119              
2120             Thrown by C or C with a predicate when no element matches.
2121              
2122             =item C
2123              
2124             Thrown by C when the selector function returns a non-array value.
2125              
2126             Internal JSON parsing error. The message includes the erroneous token or
2127             an indication of where parsing stopped.
2128              
2129             =back
2130              
2131             All other error messages are identical to L.
2132              
2133             =head1 BUGS
2134              
2135             Please report bugs to C.
2136              
2137             =head1 SEE ALSO
2138              
2139             =over 4
2140              
2141             =item * L - The LTSV counterpart of this module
2142              
2143             =item * L - The JSON encoder/decoder this module's parser is derived from
2144              
2145             =item * JSONL specification: L
2146              
2147             =item * Microsoft LINQ documentation: L
2148              
2149             =back
2150              
2151             =head1 AUTHOR
2152              
2153             INABA Hitoshi Eina@cpan.orgE
2154              
2155             =head1 COPYRIGHT AND LICENSE
2156              
2157             Copyright (c) 2026 INABA Hitoshi
2158              
2159             This library is free software; you can redistribute it and/or modify
2160             it under the same terms as Perl itself.
2161              
2162             =head1 DISCLAIMER OF WARRANTY
2163              
2164             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
2165             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT
2166             WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
2167             PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND,
2168             EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
2169             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
2170             THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS
2171             WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF
2172             ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
2173              
2174             =cut