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 )