File-Raw-JSON
view release on metacpan or search on metacpan
/* The 'json' plugin rejects STREAM with a helpful redirect. */
static int
json_stream_reject(pTHX_ FilePluginContext *ctx,
const char *chunk, size_t len, int eof)
{
PERL_UNUSED_ARG(chunk);
PERL_UNUSED_ARG(len);
PERL_UNUSED_ARG(eof);
ctx->cancel = 1;
croak("File::Raw::JSON: the 'json' plugin does not support streaming; "
"use 'jsonl' for concatenated JSON values, or slurp the whole "
"document via File::Raw::slurp(...)");
return 1;
}
/* ============================================================
* jsonl streaming
*
* Buffers bytes across chunks; brace-balancer slices off complete
* top-level values; each is parsed by yyjson and emitted via
* call_sv(ctx->callback, ...). Mirrors File::Raw::Separated's
* sep_stream pattern. State lives in ctx->call_state. */
typedef struct {
char *acc_buf;
STRLEN acc_len;
STRLEN acc_cap;
lib/File/Raw/JSON.pm view on Meta::CPAN
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:
t/10-jsonl-stream.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
use File::Raw;
use File::Raw::JSON;
use File::Temp qw(tempdir);
my $dir = tempdir(CLEANUP => 1);
# Basic streaming: each_line emits parsed values one at a time
subtest 'basic each_line + jsonl' => sub {
my $f = "$dir/s.jsonl";
File::Raw::spew($f, qq({"a":1}\n{"b":2}\n{"c":3}\n));
my @rows;
File::Raw::each_line($f, sub { push @rows, $_[0] }, plugin => 'jsonl');
is(scalar @rows, 3, 'three records emitted');
is_deeply($rows[0], {a=>1}, 'first');
is_deeply($rows[2], {c=>3}, 'third');
};
t/12-stream-rejected.t view on Meta::CPAN
use File::Temp qw(tempdir);
# each_line($p, $cb, plugin => 'json') must croak with a useful
# message - single-document JSON doesn't decompose into records.
my $dir = tempdir(CLEANUP => 1);
my $f = "$dir/doc.json";
File::Raw::spew($f, '{"a":1}');
eval { File::Raw::each_line($f, sub {}, plugin => 'json') };
like($@, qr/json.*plugin.*does not support streaming/i,
'json plugin rejects each_line with explanation');
like($@, qr/jsonl/, 'error message mentions jsonl as alternative');
done_testing;
t/14-large-document.t view on Meta::CPAN
use File::Raw::JSON;
use File::Temp qw(tempdir);
# Large document round-trip + perf sanity. Aim for a few MiB of nested
# data; nothing wild, just enough to confirm there's no quadratic
# blowup hidden in the value walker.
my $dir = tempdir(CLEANUP => 1);
# Build ~1000 records, each with a few fields; whole structure ~150 KB
# encoded. Bigger than the 64 KiB chunk size so streaming exercises
# multi-chunk behaviour where applicable.
my @records;
for my $i (1..1000) {
push @records, {
id => $i,
name => "record-$i",
tags => ["tag-$i", "category-" . ($i % 10)],
value => $i * 1.5,
meta => {
created => "2026-05-05T12:00:00Z",
t/15-ordered.t view on Meta::CPAN
$f,
qq({"z":1,"a":2}\n{"y":1,"x":2}\n{"q":1,"w":2,"e":3}\n),
);
my $rows = File::Raw::slurp($f, plugin => 'jsonl', ordered => 1);
is(scalar @$rows, 3, 'three rows');
is_deeply([keys %{$rows->[0]}], ['z','a'], 'row 1 ordered');
is_deeply([keys %{$rows->[1]}], ['y','x'], 'row 2 ordered');
is_deeply([keys %{$rows->[2]}], ['q','w','e'], 'row 3 ordered');
};
subtest 'jsonl streaming: callback receives ordered hashref per record' => sub {
my $f = "$dir/o6.jsonl";
File::Raw::spew(
$f,
qq({"z":1,"a":2}\n{"y":1,"x":2}\n),
);
my @seen;
File::Raw::each_line(
$f,
sub { push @seen, [keys %{$_[0]}] },
plugin => 'jsonl', ordered => 1,
t/21-port-multi-object-line.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
use File::Raw;
use File::Raw::JSON;
use File::Temp qw(tempdir);
# Ported from JSON-Lines-1.11/t/13-multi-object-line.t
# Multiple JSON values concatenated on one line (no separators) must
# all decode. This is what `jq -c | tee` produces under streaming.
my $dir = tempdir(CLEANUP => 1);
sub _decode {
my ($bytes) = @_;
my $f = "$dir/m.jsonl";
File::Raw::spew($f, $bytes);
return File::Raw::slurp($f, plugin => 'jsonl');
}
t/25-port-chunked-decode.t view on Meta::CPAN
is_deeply($slurped->[2], { b => 2 }, 'b=2');
is_deeply($slurped->[3], { c => 3 }, 'c=3');
is_deeply($slurped->[4], { d => 4 }, 'd=4');
is_deeply($slurped->[5]{nested}, [1,2,3], 'pretty multiline value');
# each_line yields the same sequence
my @stream;
File::Raw::each_line($f, sub { push @stream, $_[0] }, plugin => 'jsonl');
is(scalar @stream, 6, 'six records via each_line');
is($stream[1]{content}, '{ inner } { more {} }',
'string-internal braces survive chunk-by-chunk streaming');
is_deeply($stream[5]{nested}, [1,2,3],
'pretty multiline value survives streaming across newlines');
done_testing;
t/28-ordered-nested-data.t view on Meta::CPAN
my $src = qq|{"z":1,"a":2}\n{"third":3,"first":1,"second":2}\n{"only":"x"}\n|;
my $rows = file_json_decode($src, mode => 'lines', ordered => 1);
is(scalar @$rows, 3, 'three rows');
is_deeply([keys %{$rows->[0]}], [qw(z a)], 'row 0');
is_deeply([keys %{$rows->[1]}], [qw(third first second)], 'row 1');
is_deeply([keys %{$rows->[2]}], [qw(only)], 'row 2');
is(ref(tied(%{$rows->[1]})), 'Tie::OrderedHash',
'each JSONL row is its own tied OrderedHash');
};
subtest 'JSONL streaming: each_line callback receives ordered rows' => sub {
use File::Raw;
use File::Temp qw(tempfile);
my ($fh, $path) = tempfile(UNLINK => 1);
print $fh qq|{"third":3,"first":1,"second":2}\n|;
print $fh qq|{"y":2,"x":1}\n|;
print $fh qq|{"alpha":"A"}\n|;
close $fh;
my @collected;
File::Raw::each_line(
( run in 1.068 second using v1.01-cache-2.11-cpan-140bd7fdf52 )