NAME
File::Raw::JSON - JSON / JSONL plugin for File::Raw
VERSION
Version 0.01
SYNOPSIS
Loading the module registers two plugins (json, jsonl) with File::Raw at BOOT time. Use them via File::Raw's standard plugin tail:
use File::Raw qw(slurp spew each_line);
use File::Raw::JSON; # registers json + jsonl plugins
# Single JSON document
my $config = file_slurp("config.json", plugin => 'json');
# Pretty-printed write
file_spew("out.json", $payload, plugin => 'json',
pretty => 1, sort_keys => 1);
# JSON Lines / NDJSON streaming (line-by-line, RSS bounded)
file_each_line("events.jsonl",
sub { my $event = $_[0]; ... },
plugin => 'jsonl');
# Whole-file JSONL into AoV
my $events = file_slurp("events.jsonl", plugin => 'jsonl');
For in-memory bytes <-> structure work (no path, no syscalls), import the direct codec:
use File::Raw::JSON qw(file_json_decode file_json_encode);
my $val = file_json_decode($json_bytes);
my $bytes = file_json_encode($val, pretty => 1, sort_keys => 1);
# JSONL via the same entry points
my $rows = file_json_decode($jsonl_bytes, mode => 'lines');
my $out = file_json_encode(\@rows, mode => 'lines');
DESCRIPTION
Fast JSON I/O integrated with the File::Raw plugin pipeline. One syscall path through File::Raw, then a direct call into yyjson (vendored, MIT).
Two plugins are registered:
- json
-
One JSON value per file.
slurpreturns the parsed structure (hashref / arrayref / scalar / undef);spewserialises any Perl value back to JSON bytes.each_linewith this plugin croaks with a "use 'jsonl'" message - single documents don't decompose into records. - jsonl
-
JSONL / NDJSON / concatenated JSON values.
slurpreturns an AoV;spewtakes an AoV and writes one record per line;each_linestreams records via callback (memory-bounded).JSONL parsing uses brace-balancing rather than newline-splitting, mirroring JSON::Lines's
$LINESregex. This means:Pretty-printed JSONL works (records can span multiple lines).
Multiple records on one line work (
{"a":1}{"b":2}= 2 records).Braces inside string fields don't break parsing.
Chunked input is buffered and re-assembled across reads.
DIRECT CODEC
For callers who already have JSON bytes in scalar form (or who want JSON bytes back without round-tripping through a temp file), two importable XSUBs:
file_json_decode($bytes, ?key => value, ...)-
Parses
$bytesand returns the decoded Perl value. Default mode isdocument(one value per buffer); passmode => 'lines'to parse a JSONL/NDJSON buffer and get back an arrayref of values.my $val = file_json_decode($json_bytes); my $rows = file_json_decode($jsonl_bytes, mode => 'lines'); my $cfg = file_json_decode($cfg_bytes, ordered => 1, relaxed => 1);Passing
undefreturnsundef. Malformed input croaks with the yyjson error message and byte offset. file_json_encode($value, ?key => value, ...)-
Serialises
$valueand returns JSON bytes. Default mode isdocument; passmode => 'lines'with an arrayref payload to get back a JSONL buffer (one record per line, trailing\n).my $bytes = file_json_encode($val); my $jsonl = file_json_encode(\@rows, mode => 'lines'); my $diff = file_json_encode($val, pretty => 1, sort_keys => 1);
The trailing key/value list accepts the same options as the plugin path (see "OPTIONS" below). Odd-count tails croak; unknown keys croak.
These are XSUBs in the File::Raw::JSON package; importable on request via use File::Raw::JSON qw(file_json_decode file_json_encode) or use File::Raw::JSON qw(:codec). They share the same yyjson codec body as the plugin path, so output is byte-identical to a File::Raw::spew(... plugin => 'json', ...) for the same input.
OPTIONS
The standard plugin tail accepts these keys.
mode-
document(default for thejsonplugin) orlines(default forjsonl). Override the plugin's default per call. pretty-
Encode: pretty-print with newlines and indent. Default false.
indent-
Encode: spaces per indent level when
prettyis on. Must be 2 or 4 (yyjson constraint). Arbitrary indent strings planned for v0.02. sort_keys-
Encode: emit object keys in sorted order. Off by default for speed; on for diff-friendly output.
canonical-
Encode: shorthand for
sort_keys => 1+ minimal whitespace. utf8-
Treat bytes as UTF-8. Default true.
relaxed-
Decode: tolerate
//and/* */comments and trailing commas. allow_nonref-
Decode: accept top-level scalars (
42,"hi",true). Default true. allow_nan_inf-
Round-trip
NaN,Infinity,-Infinity. Non-standard JSON; default false. ordered-
Decode JSON objects as Tie::OrderedHash-tied hashes so insertion order is preserved on the Perl side. yyjson already preserves source order on parse; the regular Perl HV randomises iteration since 5.18, which is what this option works around.
my $config = file_slurp("config.json", plugin => 'json', ordered => 1); # keys(%$config) returns in document order my $events = file_slurp("trace.jsonl", plugin => 'jsonl', ordered => 1); # each $events->[$i] preserves its original key orderDecode-only flag. The encoder detects the tied storage automatically and emits keys in insertion order, so a parsed-then-re-encoded ordered structure round-trips byte-for-byte without any extra flag.
Tie::OrderedHash is a hard dependency (PREREQ_PM); it ships its own public C ABI which the encoder/decoder use to bypass Perl method dispatch entirely. Earlier versions used Tie::IxHash via
call_method("STORE")/call_method("FETCH")per key; the C ABI swap closes most of the per-key dispatch cost.Cost: ordered mode is slower than the default HV path because maintaining insertion order requires bookkeeping the encoder/decoder otherwise wouldn't do. Indicative numbers on a 10k-record JSONL fixture (10 fields each):
decode default vs decode ordered: ~4x slower encode default vs encode ordered: ~1.25x slower (C ABI iterator) round-trip vs round-trip: ~3x slowerThe encode side is essentially free; the decode side pays the AV/HV bookkeeping that maintains the insertion-ordered key list. Use the option only when order actually matters; the default is the fast path.
boolean_class-
Class to bless decoded JSON true/false into. Default
File::Raw::JSON::Boolean. The encoder also recognisesJSON::PP::Boolean,Types::Serialiser::Boolean,Cpanel::JSON::XS::Boolean,JSON::XS::Boolean, and thebooleanmodule by name string. max_depth-
Decode: croak on nesting deeper than this. Default 512.
eol-
JSONL only: line terminator on encode. 1-3 bytes, default
\n.
VALUE MAPPING
JSON Perl Encoded back as
---- ---- ---------------
null undef null
true / false blessed sentinel matching literal
integer (IV) IV integer
integer (UV) UV integer
float NV float (shortest round-trip)
string utf8-decoded SV JSON string
array AV ref [ ... ]
object HV ref { ... }
STREAMING
each_line($path, $cb, plugin => 'jsonl') opens the file via File::Raw's STREAM dispatch, reads in 64 KiB chunks, accumulates bytes in a per-call buffer, and uses a brace-balancing state machine to slice off complete top-level values. Each value is parsed and passed to your callback. RSS is bounded by the read buffer + the largest single JSON value in the stream.
To abort mid-stream, die from the callback. The exception propagates and the buffer is freed.
SEE ALSO
File::Raw, JSON::Lines, https://jsonlines.org/, https://github.com/ibireme/yyjson.
AUTHOR
LNATION, <thisusedtobeanemail at gmail.com>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION.
This is free software, licensed under the Artistic License 2.0 (GPL Compatible).
The bundled yyjson library is Copyright (c) 2020 YaoYuan, MIT licensed - see LICENSE.yyjson.