File-Raw-JSON

 view release on metacpan or  search on metacpan

JSON.xs  view on Meta::CPAN


/* 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 )