And remove UTF-8 check in JSON writer. It honestly feels kind of silly to do that validation there while I've never done similar validations in any other output routines - including this XML writer. FU::XMLWriter is a copy of TUWF::XMLXS with a bunch of improvements applied: now uses refcounts to determine the current output instance, auto-generates XS functions and has faster escaped string output - inspired by the JSON writer. TODO: - Integrate into FU - Do something with bool attribute values - Benchmarks - Should $content be optional for all tags? The reason they weren't in TUWF::XMLXS is because TUWF::XML supports opening tags without closing them, but that idea turned out to suck and isn't supported anymore. This is hopefully the last XS module for the FU framework. The only C code being written now should be bug fixes and extending FU::Pg with some planned features. Already ended up with more C than I had planned...
167 lines
4.3 KiB
Perl
167 lines
4.3 KiB
Perl
use v5.36;
|
|
use experimental 'builtin', 'for_list';
|
|
use builtin 'true', 'false';
|
|
use Test::More;
|
|
use Tie::Array;
|
|
use Tie::Hash;
|
|
use FU::Util 'json_format';
|
|
|
|
|
|
sub MyToJSON::TO_JSON { [scalar @_, ref $_[0], ${$_[0]}] }
|
|
sub MyToJSONSelf::TO_JSON { $_[0] }
|
|
|
|
my @tests = (
|
|
undef, 'null',
|
|
true, 'true',
|
|
false, 'false',
|
|
(map +($_, $_),
|
|
-1000..1000,
|
|
12345, 123456, 1234567,
|
|
98765, 987654, 9876543,
|
|
-12345, -123456, -1234567,
|
|
-98765, -987654, -9876543,
|
|
-9223372036854775808,
|
|
9223372036854775807,
|
|
18446744073709551615,
|
|
),
|
|
0.1, '0.1',
|
|
0.000123, '0.000123',
|
|
-1e100, '-1e+100',
|
|
|
|
do { use utf8; (
|
|
"\x01é\r\n\x1f💩", '"\u0001é\r\n\u001f💩"',
|
|
)},
|
|
|
|
"\x011\r\n\x8c", "\"\\u00011\\r\\n\x8c\"",
|
|
"\xff\xff", "\"\xff\xff\"",
|
|
"\x{1f4a9}", do { use utf8; '"💩"' },
|
|
|
|
[], '[]',
|
|
[0,1], '[0,1]',
|
|
[true,'hi',-0.123, [undef]], '[true,"hi",-0.123,[null]]',
|
|
do { tie my @a, 'Tie::StdArray'; @a = (1,2); \@a }, '[1,2]',
|
|
|
|
{}, '{}',
|
|
{'a',1}, '{"a":1}',
|
|
do { tie my %h, 'Tie::StdHash'; %h = ('b',1); \%h }, '{"b":1}',
|
|
do { tie my %h, 'MyOrderedHash', one => 1, two => undef, three => []; \%h }, '{"one":1,"two":null,"three":[]}',
|
|
|
|
do { my $o = [true]; bless \$o, 'MyToJSON' }, '[1,"MyToJSON",[true]]',
|
|
do { my $x = [true]; my $o = [bless \$x, 'MyToJSON']; bless \$o, 'MyToJSON' }, '[1,"MyToJSON",[[1,"MyToJSON",[true]]]]',
|
|
|
|
# from http://e-choroba.eu/18-yapc
|
|
$$, $$,
|
|
''.$$, '"'.$$.'"',
|
|
do { my $x = 12; utf8::decode($x); $x }, '"12"',
|
|
do { no warnings 'numeric'; my $x = '19a'; $x += 0; $x }, '19',
|
|
1844674407370955161 / 10, '1.84467440737096e+17',
|
|
);
|
|
|
|
my @errors = (
|
|
\1, qr/unable to format reference/,
|
|
*STDOUT, qr/unable to format unknown value/,
|
|
'NaN'+0, qr/unable to format floating point NaN or Inf as JSON/,
|
|
'Inf'+0, qr/unable to format floating point NaN or Inf as JSON/,
|
|
do { my $o = {}; bless $o, 'FU::Whatever' }, qr/unable to format 'FU::Whatever' object as JSON/,
|
|
do { my $o = {}; bless $o, 'MyToJSONSelf' }, qr/MyToJSONSelf::TO_JSON method returned same object as was passed instead of a new one/,
|
|
);
|
|
|
|
|
|
for my($in, $exp) (@tests) {
|
|
my $out = json_format $in;
|
|
is $out, $exp;
|
|
ok utf8::is_utf8($out);
|
|
|
|
$out = json_format $in, utf8 => 1;
|
|
utf8::encode(my $uexp = $exp);
|
|
is $out, $uexp;
|
|
ok !utf8::is_utf8($out);
|
|
}
|
|
|
|
for my ($in, $exp) (@errors) {
|
|
eval { json_format $in };
|
|
like $@, $exp;
|
|
}
|
|
|
|
|
|
is json_format({qw/a 1 b 2 c 3 d 4 d1 5 d11 6/, do { use utf8; qw/ü 7 月 8 💩 9/ }}, canonical => 1),
|
|
do { use utf8; '{"a":"1","b":"2","c":"3","d":"4","d1":"5","d11":"6","ü":"7","月":"8","💩":"9"}' };
|
|
|
|
is json_format(
|
|
{ a => [], b => {}, c => { x => 1 }, d => { y => true, z => false }, e => [1,2,3] },
|
|
canonical => 1, pretty => 1
|
|
), <<_;
|
|
{
|
|
"a" : [],
|
|
"b" : {},
|
|
"c" : {
|
|
"x" : 1
|
|
},
|
|
"d" : {
|
|
"y" : true,
|
|
"z" : false
|
|
},
|
|
"e" : [
|
|
1,
|
|
2,
|
|
3
|
|
]
|
|
}
|
|
_
|
|
|
|
|
|
eval { json_format [[]], max_depth => 2 };
|
|
like $@, qr/max_depth exceeded while formatting JSON/;
|
|
|
|
eval { json_format 'hello world', max_size => 8 };
|
|
like $@, qr/maximum string length exceeded/;
|
|
|
|
|
|
# Test large strings to cover some buffer handling special cases.
|
|
for (2000..2100, 4000..4200, 8100..8200, 12200..12300, 16300..16400) {
|
|
my $s = 'a'x$_;
|
|
is json_format($s), "\"$s\"";
|
|
}
|
|
|
|
# 500 depth
|
|
{
|
|
my $v = 1;
|
|
$v = [$v] for (1..500);
|
|
is json_format($v), '['x500 . 1 . ']'x500;
|
|
}
|
|
{
|
|
my $v = 1;
|
|
$v = {'',$v} for (1..500);
|
|
is json_format($v), '{"":'x500 . 1 . '}'x500;
|
|
}
|
|
|
|
|
|
# http://e-choroba.eu/18-yapc slide 6
|
|
|
|
tie my $incs, 'MyIncrementer', 'Xa';
|
|
is json_format($incs), '"Xa"';
|
|
is json_format($incs), '"Xb"';
|
|
is json_format($incs), '"Xc"';
|
|
|
|
tie my $incu, 'MyIncrementer', 4;
|
|
is json_format($incu), 4;
|
|
is json_format($incu), 5;
|
|
is json_format($incu), 6;
|
|
|
|
|
|
done_testing;
|
|
|
|
package MyIncrementer;
|
|
use Tie::Scalar;
|
|
use parent -norequire => 'Tie::StdScalar';
|
|
sub TIESCALAR { my ($class, $val) = @_; bless \$val, $class }
|
|
sub FETCH { my $s = shift; $$s++ }
|
|
|
|
|
|
package MyOrderedHash;
|
|
sub TIEHASH { shift; bless [ [ map $_[$_*2], 0..$#_/2 ], +{@_}, 0 ], __PACKAGE__ };
|
|
sub FETCH { $_[0][1]{$_[1]} }
|
|
sub EXISTS { exists $_[0][1]{$_[1]} }
|
|
sub FIRSTKEY { $_[0][2] = 0; &NEXTKEY }
|
|
sub NEXTKEY { $_[0][0][ $_[0][2]++ ] }
|
|
sub SCALAR { scalar $_[0][0]->@* }
|