File-Raw-JSON

 view release on metacpan or  search on metacpan

t/28-ordered-nested-data.t  view on Meta::CPAN


subtest 'mutate: insert key after decode -> re-encode appends at end' => sub {
    my $r = file_json_decode(q|{"alpha":1,"beta":2}|, ordered => 1);
    $r->{gamma} = 3;
    is_deeply([keys %$r], [qw(alpha beta gamma)],
              'inserted key appears at end');
    my $bytes = file_json_encode($r);
    is($bytes, '{"alpha":1,"beta":2,"gamma":3}',
       're-encode emits inserted key at end');
};

subtest 'mutate: update existing key preserves position' => sub {
    my $r = file_json_decode(q|{"alpha":1,"beta":2,"gamma":3}|, ordered => 1);
    $r->{beta} = 'updated';
    is_deeply([keys %$r], [qw(alpha beta gamma)],
              'updated key keeps its position');
    is($r->{beta}, 'updated', 'value updated');
    my $bytes = file_json_encode($r);
    is($bytes, '{"alpha":1,"beta":"updated","gamma":3}',
       're-encode reflects update without reordering');
};

subtest 'mutate: delete key removes it, others preserved' => sub {
    my $r = file_json_decode(q|{"a":1,"b":2,"c":3,"d":4}|, ordered => 1);
    delete $r->{b};
    is_deeply([keys %$r], [qw(a c d)],
              'remaining keys in original order');
    my $bytes = file_json_encode($r);
    is($bytes, '{"a":1,"c":3,"d":4}',
       're-encode reflects deletion');

    # Insert after delete: lands at the end.
    $r->{e} = 5;
    is(file_json_encode($r), '{"a":1,"c":3,"d":4,"e":5}',
       'insert after delete lands at end');
};

subtest 'mutate inside a nested ordered hash' => sub {
    my $src = q|{"top":{"x":1,"y":2,"z":3}}|;
    my $r = file_json_decode($src, ordered => 1);
    $r->{top}{w} = 4;       # add to inner
    delete $r->{top}{y};    # delete from inner
    is_deeply([keys %{$r->{top}}], [qw(x z w)],
              'nested mutations: insert at end, delete preserves rest');
    my $bytes = file_json_encode($r);
    is($bytes, '{"top":{"x":1,"z":3,"w":4}}',
       're-encoded structure reflects nested mutations');
};

subtest 'JSONL slurp: each row independently ordered' => sub {
    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(
        $path,
        sub { my $row = $_[0]; push @collected, [keys %$row] },
        plugin => 'jsonl', ordered => 1,
    );
    is_deeply($collected[0], [qw(third first second)], 'streamed row 0');
    is_deeply($collected[1], [qw(y x)],                 'streamed row 1');
    is_deeply($collected[2], [qw(alpha)],               'streamed row 2');
};

subtest 'sort_keys overrides ordered at every level' => sub {
    # ordered=>1 is decode; sort_keys=>1 is encode.  When you
    # decode with ordered (so internal keys are insertion-ordered)
    # and then re-encode with sort_keys, the *output* should be
    # alphabetically sorted at every level - sort_keys wins.
    my $src = q|{"z":{"y":2,"x":1},"a":[{"q":1,"p":2}]}|;
    my $r = file_json_decode($src, ordered => 1);

    my $sorted = file_json_encode($r, sort_keys => 1);
    is($sorted, '{"a":[{"p":2,"q":1}],"z":{"x":1,"y":2}}',
       'sort_keys flattens the order tree on encode');

    # Without sort_keys, the source order is preserved.
    my $natural = file_json_encode($r);
    is($natural, '{"z":{"y":2,"x":1},"a":[{"q":1,"p":2}]}',
       'no sort_keys: source order preserved on encode');
};

subtest 'deep ordered: 5-level nested objects, all keys preserved' => sub {
    # Each of the 5 nesting levels is its own ordered hash with
    # deliberately non-alphabetical keys.
    my $src = q|{
        "L0_z":1,
        "L0_a":{"L1_y":2,"L1_b":{"L2_x":3,"L2_c":{"L3_w":4,"L3_d":{"L4_v":5,"L4_e":6}}}}
    }|;
    my $r = file_json_decode($src, ordered => 1);

    is_deeply([keys %$r],                                    [qw(L0_z L0_a)], 'L0');
    is_deeply([keys %{$r->{L0_a}}],                          [qw(L1_y L1_b)], 'L1');
    is_deeply([keys %{$r->{L0_a}{L1_b}}],                    [qw(L2_x L2_c)], 'L2');
    is_deeply([keys %{$r->{L0_a}{L1_b}{L2_c}}],              [qw(L3_w L3_d)], 'L3');
    is_deeply([keys %{$r->{L0_a}{L1_b}{L2_c}{L3_d}}],        [qw(L4_v L4_e)], 'L4');
};

subtest 'ordered hash inside array inside ordered hash (alternation)' => sub {
    my $src = q|{"outer_z":1,"outer_a":[{"r":1,"q":2,"p":3},{"only":"x"}],"outer_m":2}|;
    my $r = file_json_decode($src, ordered => 1);

    is_deeply([keys %$r], [qw(outer_z outer_a outer_m)],
              'top-level ordered keys preserved');
    is_deeply([keys %{$r->{outer_a}[0]}], [qw(r q p)],



( run in 0.516 second using v1.01-cache-2.11-cpan-140bd7fdf52 )