NAME
JSON::LINQ - LINQ-style query interface for JSON and JSONL files
VERSION
Version 1.00
SYNOPSIS
use JSON::LINQ;
# Read JSON file (array of objects) and query
my @results = JSON::LINQ->FromJSON("users.json")
->Where(sub { $_[0]{age} >= 18 })
->Select(sub { $_[0]{name} })
->Distinct()
->ToArray();
# Read JSONL (JSON Lines) file - one JSON object per line
my @errors = JSON::LINQ->FromJSONL("events.jsonl")
->Where(sub { $_[0]{level} eq 'ERROR' })
->ToArray();
# DSL syntax for simple filtering
my @active = JSON::LINQ->FromJSON("users.json")
->Where(status => 'active')
->ToArray();
# Grouping and aggregation
my @stats = JSON::LINQ->FromJSON("orders.json")
->GroupBy(sub { $_[0]{category} })
->Select(sub {
my $g = shift;
return {
Category => $g->{Key},
Count => scalar(@{$g->{Elements}}),
Total => JSON::LINQ->From($g->{Elements})
->Sum(sub { $_[0]{amount} }),
};
})
->OrderByDescending(sub { $_[0]{Total} })
->ToArray();
# Write results back as JSON or JSONL
JSON::LINQ->From(\@results)->ToJSON("output.json");
JSON::LINQ->From(\@results)->ToJSONL("output.jsonl");
# Boolean values
my $rec = { active => JSON::LINQ::true, count => 0 };
JSON::LINQ->From([$rec])->ToJSON("output.json");
# ToJSON encodes as: {"active":true,"count":0}
TABLE OF CONTENTS
"INCLUDED DOCUMENTATION" -- eg/ samples and doc/ cheat sheets
"METHODS" -- Complete method reference (63 methods)
"EXAMPLES" -- Practical examples
"FEATURES" -- Lazy evaluation, method chaining, DSL
"ARCHITECTURE" -- Iterator design, execution flow
"COMPATIBILITY" -- Perl 5.005+ support, pure Perl
"DIAGNOSTICS" -- Error messages
DESCRIPTION
JSON::LINQ provides a LINQ-style query interface for JSON and JSONL (JSON Lines) files. It is the JSON counterpart of LTSV::LINQ, sharing the same 60-method LINQ API and adding three JSON-specific I/O methods.
Key features:
Lazy evaluation - O(1) memory for JSONL streaming; JSON arrays are loaded once then iterated lazily
Method chaining - Fluent, readable query composition
DSL syntax - Simple key-value filtering
63 LINQ methods - 60 from LTSV::LINQ + FromJSON, FromJSONL, FromJSONString, ToJSON, ToJSONL
Pure Perl - No XS dependencies
Perl 5.005_03+ - Works on ancient and modern Perl
Built-in JSON parser - No CPAN JSON module required
Supported Data Sources
FromJSON($file) - JSON file containing a top-level array or object
FromJSONL($file) - JSONL file (one JSON value per line)
FromJSONString($json) - JSON string (array or object)
From(\@array) - In-memory Perl array
Range($start, $count) - Integer sequence
Empty() - Empty sequence
Repeat($element, $count) - Repeated element
What is JSONL?
JSONL (JSON Lines, also known as ndjson - newline-delimited JSON) is a text format where each line is a valid JSON value (typically an object). It is particularly suited for log files and streaming data because:
One record per line enables streaming with O(1) memory usage
Compatible with standard Unix tools (grep, sed, awk)
Easily appendable without rewriting the whole file
Each line is independently parseable
Format example:
{"time":"2026-04-20T10:00:00","host":"192.0.2.1","status":200,"url":"/"}
{"time":"2026-04-20T10:00:01","host":"192.0.2.2","status":404,"url":"/missing"}
FromJSONL reads these files lazily (one line at a time), matching the memory efficiency of LTSV::LINQ's FromLTSV.
What is LINQ?
LINQ (Language Integrated Query) is the Microsoft .NET query API. This module brings the same 60-method LINQ interface to JSON data in Perl. See LTSV::LINQ for a detailed description of the LINQ design philosophy.
INCLUDED DOCUMENTATION
The eg/ directory contains sample programs:
eg/01_json_query.pl FromJSON/Where/Select/OrderByDescending/Distinct/ToLookup
eg/02_jsonl_query.pl FromJSONL streaming, GroupBy, aggregation
eg/03_grouping.pl GroupBy, ToLookup, GroupJoin, SelectMany
eg/04_sorting.pl OrderBy/ThenBy multi-key sort
The doc/ directory contains JSON::LINQ cheat sheets in 21 languages:
doc/json_linq_cheatsheet.EN.txt English
doc/json_linq_cheatsheet.JA.txt Japanese
doc/json_linq_cheatsheet.ZH.txt Chinese (Simplified)
doc/json_linq_cheatsheet.TW.txt Chinese (Traditional)
doc/json_linq_cheatsheet.KO.txt Korean
doc/json_linq_cheatsheet.FR.txt French
doc/json_linq_cheatsheet.ID.txt Indonesian
doc/json_linq_cheatsheet.VI.txt Vietnamese
doc/json_linq_cheatsheet.TH.txt Thai
doc/json_linq_cheatsheet.HI.txt Hindi
doc/json_linq_cheatsheet.BN.txt Bengali
doc/json_linq_cheatsheet.TR.txt Turkish
doc/json_linq_cheatsheet.MY.txt Malay
doc/json_linq_cheatsheet.TL.txt Filipino
doc/json_linq_cheatsheet.KM.txt Khmer
doc/json_linq_cheatsheet.MN.txt Mongolian
doc/json_linq_cheatsheet.NE.txt Nepali
doc/json_linq_cheatsheet.SI.txt Sinhala
doc/json_linq_cheatsheet.UR.txt Urdu
doc/json_linq_cheatsheet.UZ.txt Uzbek
doc/json_linq_cheatsheet.BM.txt Burmese
METHODS
Complete Method Reference
This module implements 63 LINQ methods organized into 15 categories. In addition, true and false boolean accessor functions are provided.
Data Sources (7): From, FromJSON, FromJSONL, FromJSONString, Range, Empty, Repeat
Filtering (1): Where (with DSL)
Projection (2): Select, SelectMany
Concatenation (2): Concat, Zip
Partitioning (4): Take, Skip, TakeWhile, SkipWhile
Ordering (13): OrderBy, OrderByDescending, OrderByStr, OrderByStrDescending, OrderByNum, OrderByNumDescending, Reverse, ThenBy, ThenByDescending, ThenByStr, ThenByStrDescending, ThenByNum, ThenByNumDescending
Grouping (1): GroupBy
Set Operations (4): Distinct, Union, Intersect, Except
Join (2): Join, GroupJoin
Quantifiers (3): All, Any, Contains
Comparison (1): SequenceEqual
Element Access (8): First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAt, ElementAtOrDefault
Aggregation (7): Count, Sum, Min, Max, Average, AverageOrDefault, Aggregate
Conversion (8): ToArray, ToList, ToDictionary, ToLookup, ToJSON, ToJSONL, DefaultIfEmpty
Utility (1): ForEach
JSON-Specific Data Source Methods
- FromJSON($filename)
-
Read a JSON file containing a top-level array of values. Each element of the array becomes one item in the sequence.
my $q = JSON::LINQ->FromJSON("users.json");If the file contains a single JSON object (not an array), it is treated as a one-element sequence.
File format:
[ {"name": "Alice", "age": 30}, {"name": "Bob", "age": 25} ]The entire file is read into memory and parsed once. For large files, consider JSONL format with
FromJSONLfor streaming access. - FromJSONL($filename)
-
Read a JSONL (JSON Lines) file. Each non-empty line is parsed as a separate JSON value. Empty lines and lines starting with
#are skipped.my $q = JSON::LINQ->FromJSONL("events.jsonl");File format:
{"event":"login","user":"alice","ts":1713600000} {"event":"purchase","user":"alice","ts":1713600060,"amount":29.99} {"event":"logout","user":"alice","ts":1713600120}FromJSONLreads lazily (one line at a time), providing O(1) memory usage for arbitrarily large files.Invalid JSON lines produce a warning and are skipped rather than aborting the entire sequence.
- FromJSONString($json)
-
Create a query from a JSON string. Accepts a JSON array (each element becomes one sequence item) or a JSON object (single-element sequence).
my $q = JSON::LINQ->FromJSONString('[{"id":1},{"id":2}]'); my $q = JSON::LINQ->FromJSONString('{"id":1,"name":"Alice"}');
JSON-Specific Conversion Methods
- ToJSON($filename)
-
Write the sequence as a JSON file containing a JSON array. Each element is encoded as JSON. The output is a valid JSON array.
$query->ToJSON("output.json");Output format:
[ {"age":30,"name":"Alice"}, {"age":25,"name":"Bob"} ]Hash keys are sorted alphabetically for deterministic output.
- ToJSONL($filename)
-
Write the sequence as a JSONL file. Each element is written as one line of JSON. This is the streaming counterpart of
ToJSON.$query->ToJSONL("output.jsonl");Output format:
{"age":30,"name":"Alice"} {"age":25,"name":"Bob"}
Boolean Values
JSON::LINQ provides boolean singleton objects compatible with JSON encoding:
JSON::LINQ::true # stringifies as "true", numifies as 1
JSON::LINQ::false # stringifies as "false", numifies as 0
Use these when creating data structures that will be serialised to JSON:
my $rec = { active => JSON::LINQ::true, count => 0 };
# ToJSON encodes as: {"active":true,"count":0}
When FromJSON or FromJSONL decode a JSON true or false, the result is a JSON::LINQ::Boolean object that behaves as 1 or 0 in numeric and boolean context.
All Other Methods
All 60 LINQ methods from LTSV::LINQ are available unchanged. Please refer to LTSV::LINQ for complete documentation of:
Where, Select, SelectMany, Concat, Zip, Take, Skip, TakeWhile, SkipWhile, OrderBy, OrderByDescending, OrderByStr, OrderByStrDescending, OrderByNum, OrderByNumDescending, Reverse, ThenBy, ThenByDescending, ThenByStr, ThenByStrDescending, ThenByNum, ThenByNumDescending, GroupBy, Distinct, Union, Intersect, Except, Join, GroupJoin, All, Any, Contains, SequenceEqual, First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAt, ElementAtOrDefault, Count, Sum, Min, Max, Average, AverageOrDefault, Aggregate, ToArray, ToList, ToDictionary, ToLookup, DefaultIfEmpty, ForEach.
EXAMPLES
Basic JSON File Query
use JSON::LINQ;
# users.json: [{"name":"Alice","age":30}, {"name":"Bob","age":25}, ...]
my @adults = JSON::LINQ->FromJSON("users.json")
->Where(sub { $_[0]{age} >= 18 })
->OrderBy(sub { $_[0]{name} })
->ToArray();
JSONL Streaming
# events.jsonl: one JSON object per line
my $error_count = JSON::LINQ->FromJSONL("events.jsonl")
->Count(sub { $_[0]{level} eq 'ERROR' });
JSON::LINQ->FromJSONL("events.jsonl")
->Where(sub { $_[0]{level} eq 'ERROR' })
->ForEach(sub { print $_[0]{message}, "\n" });
Aggregation
my $avg = JSON::LINQ->FromJSON("orders.json")
->Where(sub { $_[0]{status} eq 'completed' })
->Average(sub { $_[0]{amount} });
printf "Average order: %.2f\n", $avg;
Grouping
my @by_category = JSON::LINQ->FromJSON("products.json")
->GroupBy(sub { $_[0]{category} })
->Select(sub {
my $g = shift;
{
Category => $g->{Key},
Count => scalar(@{$g->{Elements}}),
MaxPrice => JSON::LINQ->From($g->{Elements})
->Max(sub { $_[0]{price} }),
}
})
->OrderByDescending(sub { $_[0]{Count} })
->ToArray();
Transform and Write
# Read JSON, transform, write back as JSONL
JSON::LINQ->FromJSON("input.json")
->Select(sub {
my $r = shift;
return { %$r, processed => JSON::LINQ::true };
})
->ToJSONL("output.jsonl");
In-Memory Array Query
my @data = (
{name => 'Alice', score => 95},
{name => 'Bob', score => 72},
{name => 'Carol', score => 88},
);
my @top = JSON::LINQ->From(\@data)
->Where(sub { $_[0]{score} >= 80 })
->OrderByDescending(sub { $_[0]{score} })
->ToArray();
FEATURES
Lazy Evaluation
FromJSONL reads one line at a time. Combined with Where and Take, only the needed records are ever in memory simultaneously.
FromJSON reads the whole file once but then iterates the array lazily.
Built-in JSON Parser
JSON::LINQ contains its own JSON encoder/decoder (derived from mb::JSON 0.06). No CPAN JSON module is required. The parser handles:
UTF-8 multibyte strings (output as-is, not \uXXXX-escaped)
\uXXXXescape sequences on input (converted to UTF-8)All JSON types: object, array, string, number, true, false, null
Nested structures of arbitrary depth
ARCHITECTURE
Relationship to LTSV::LINQ
JSON::LINQ and LTSV::LINQ are parallel modules sharing the same LINQ API.
LTSV::LINQ - LINQ for LTSV (Labeled Tab-Separated Values) files
JSON::LINQ - LINQ for JSON and JSONL files
Both provide the same 60 LINQ methods. JSON::LINQ adds:
FromJSON($file) - read JSON array file
FromJSONL($file) - read JSONL file (streaming)
FromJSONString($json) - read JSON string
ToJSON($file) - write JSON array file
ToJSONL($file) - write JSONL file
The internal iterator architecture is identical: each operator returns a new query object wrapping a closure.
Memory Characteristics
FromJSONL - O(1) per record: one line at a time
FromJSON - O(n): entire file loaded once, then lazy iteration
ToJSON - O(n): entire sequence collected for array output
ToJSONL - O(1) per record: streaming write
COMPATIBILITY
Perl Version Support
Compatible with Perl 5.00503 and later. See LTSV::LINQ for the full compatibility rationale (Universal Consensus 1998 / Perl 5.005_03).
Pure Perl Implementation
No XS dependencies. No CPAN module dependencies. Works on any Perl installation with only the standard core.
JSON Limitations
The built-in parser has the same limitations as mb::JSON 0.06:
Surrogate pairs (
\uD800-\uDFFF) are not supportedCircular references in encoding cause infinite recursion
Non-ARRAY/HASH references are stringified
DIAGNOSTICS
JSON::LINQ::FromJSON: cannot parse '$file': ...-
The file exists but does not contain valid JSON.
JSON::LINQ::FromJSON: '$file' must contain a JSON array or object-
The file contains valid JSON but the top-level value is a string, number, or boolean, not an array or object.
JSON::LINQ::FromJSONL: skipping invalid JSON line: ...-
A line in a JSONL file could not be parsed. The line is skipped with a warning; processing continues.
JSON::LINQ::_json_decode: ...-
Internal JSON parsing error. The message includes the specific unexpected token or an indication of where parsing stopped.
JSON::LINQ::FromJSON: cannot parse '$file': $@-
The file exists but its content is not valid JSON.
JSON::LINQ::FromJSONString: cannot parse JSON: $@-
The supplied JSON string is not valid JSON.
JSON::LINQ::_json_decode: expected ',' or ']' in array-
The JSON array was not properly terminated or separated.
JSON::LINQ::_json_decode: expected ',' or '}' in object-
A JSON object was not properly terminated or separated.
JSON::LINQ::_json_decode: expected ':' after key '$key'-
The colon separator was missing after a JSON object key.
JSON::LINQ::_json_decode: expected string key in object-
A JSON object key was not a quoted string.
JSON::LINQ::_json_decode: trailing garbage:-
Extra text was found after a successfully parsed top-level JSON value.
JSON::LINQ::_json_decode: unexpected end of input-
The JSON text ended before a complete value was parsed.
JSON::LINQ::_json_decode: unexpected token:-
An unrecognised token was encountered while parsing JSON.
JSON::LINQ::_json_decode: unterminated string-
A JSON string was not closed with a double-quote.
Cannot open '$file': $!-
Thrown by
FromJSONorFromJSONLwhen the specified file cannot be opened. Cannot open '$filename': $!-
Thrown by
ToJSONorToJSONLwhen the output file cannot be opened. From() requires ARRAY reference-
Thrown by
From()when the argument is not an array reference. Index must be non-negative-
Thrown by
ElementAt()when the supplied index is less than zero. Index out of range-
Thrown by
ElementAt()when the index is beyond the end of the sequence. UseElementAtOrDefault()to avoid this error. Invalid number of arguments for Aggregate-
Thrown by
Aggregate()when called with an argument count other than 1, 2, or 3. Sequence contains no elements-
Thrown by
First(),Last(),Average(),Aggregate()(no-seed form), andSingle()when the sequence is empty or no element satisfies the predicate. Sequence contains more than one element-
Thrown by
Single()when more than one element (or matching element) is found. No element satisfies the condition-
Thrown by
First()orLast()with a predicate when no element matches. SelectMany: selector must return an ARRAY reference-
Thrown by
SelectMany()when the selector function returns a non-array value.Internal JSON parsing error. The message includes the erroneous token or an indication of where parsing stopped.
All other error messages are identical to LTSV::LINQ.
BUGS
Please report bugs to ina@cpan.org.
SEE ALSO
LTSV::LINQ - The LTSV counterpart of this module
mb::JSON - The JSON encoder/decoder this module's parser is derived from
JSONL specification: https://jsonlines.org/
Microsoft LINQ documentation: https://learn.microsoft.com/en-us/dotnet/csharp/linq/
AUTHOR
INABA Hitoshi <ina@cpan.org>
COPYRIGHT AND LICENSE
Copyright (c) 2026 INABA Hitoshi
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
DISCLAIMER OF WARRANTY
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.