我将有一个可能非常大的JSON文件,我想从中流式传输而不是将其全部加载到内存中。 根据JSON::XS
的以下陈述(我添加了重点),我认为它不适合我的需求。 是否有一个Perl 5 JSON模块可以从磁盘流式传输结果?
在某些情况下,需要对 JSON 文本进行增量解析。虽然此模块始终必须同时将 JSON 文本和生成的 Perl 数据结构保存在内存中,但它确实允许您以增量方式解析 JSON 流。它通过累积文本来实现这一点,直到它有一个完整的 JSON 对象,然后它可以解码。此过程类似于使用decode_prefix查看完整的 JSON 对象是否可用,但效率更高(并且可以通过最少的方法调用来实现)。
为了澄清,JSON 将包含一个对象数组。 我想一次从文件中读取一个对象。
在易用性和速度方面,JSON::SL
似乎是赢家:
#!/usr/bin/perl
use strict;
use warnings;
use JSON::SL;
my $p = JSON::SL->new;
#look for everthing past the first level (i.e. everything in the array)
$p->set_jsonpointer(["/^"]);
local $/ = 5; #read only 5 bytes at a time
while (my $buf = <DATA>) {
$p->feed($buf); #parse what you can
#fetch anything that completed the parse and matches the JSON Pointer
while (my $obj = $p->fetch) {
print "$obj->{Value}{n}: $obj->{Value}{s}n";
}
}
__DATA__
[
{ "n": 0, "s": "zero" },
{ "n": 1, "s": "one" },
{ "n": 2, "s": "two" }
]
JSON::Streaming::Reader
还可以,但它速度较慢,并且界面过于冗长(即使许多代码引用什么都不做,也需要所有这些代码引用):
#!/usr/bin/perl
use strict;
use warnings;
use JSON::Streaming::Reader;
my $p = JSON::Streaming::Reader->for_stream(*DATA);
my $obj;
my $attr;
$p->process_tokens(
start_array => sub {}, #who cares?
end_array => sub {}, #who cares?
end_property => sub {}, #who cares?
start_object => sub { $obj = {}; }, #clear the current object
start_property => sub { $attr = shift; }, #get the name of the attribute
#add the value of the attribute to the object
add_string => sub { $obj->{$attr} = shift; },
add_number => sub { $obj->{$attr} = shift; },
#object has finished parsing, it can be used now
end_object => sub { print "$obj->{n}: $obj->{s}n"; },
);
__DATA__
[
{ "n": 0, "s": "zero" },
{ "n": 1, "s": "one" },
{ "n": 2, "s": "two" }
]
要解析 1,000 条记录,需要 JSON::SL
.2 秒和 JSON::Streaming::Reader
3.6 秒(注意,JSON::SL
一次输入 4k,我无法控制 JSON::Streaming::Reader 的缓冲区大小)。
您是否查看过在 search.cpan.org 上搜索"JSON Stream"时首先显示为第一个的 JSON::Streaming::Reader?
或者,通过搜索"JSON SAX"找到 JSON::SL - 不是那么明显的搜索词,但您描述的内容听起来像是 XML 的 SAX 解析器。
它通过累积文本来实现这一点,直到它有一个完整的 JSON 对象,然后它可以解码。
这就是搞砸你的原因。JSON 文档是一个对象。
您需要更清楚地定义您希望从增量解析中获得的内容。您是否正在寻找大型映射的一个元素?你想用你读出/写的信息做什么?
我不知道有任何库可以通过一次从数组中读取一个元素来增量解析 JSON 数据。然而,使用有限状态自动机实现自己非常简单(基本上你的文件具有s*[s*([^,]+,)*([^,]+)?s*]s*
格式,除了你需要正确解析字符串中的逗号。
您是否尝试先跳过右刹车[
,然后跳过逗号,
:
$json->incr_text =~ s/^ s* [ //x;
...
$json->incr_text =~ s/^ s* , //x;
...
$json->incr_text =~ s/^ s* ] //x;
就像第三个例子:http://search.cpan.org/dist/JSON-XS/XS.pm#EXAMPLES
如果你可以控制你如何生成你的JSON,那么我建议关闭漂亮的格式,每行打印一个对象。 这使得解析变得简单,如下所示:
use Data::Dumper;
use JSON::Parse 'json_to_perl';
use JSON;
use JSON::SL;
my $json_sl = JSON::SL->new();
use JSON::XS;
my $json_xs = JSON::XS->new();
$json_xs = $json_xs->pretty(0);
#$json_xs = $json_xs->utf8(1);
#$json_xs = $json_xs->ascii(0);
#$json_xs = $json_xs->allow_unknown(1);
my ($file) = @ARGV;
unless( defined $file && -f $file )
{
print STDERR "usage: $0 FILEn";
exit 1;
}
my @cmd = ( qw( CMD ARGS ), $file );
open my $JSON, '-|', @cmd or die "Failed to exec @cmd: $!";
# local $/ = 4096; #read 4k at a time
while( my $line = <$JSON> )
{
if( my $obj = json($line) )
{
print Dumper($obj);
}
else
{
die "error: failed to parse line - $line";
}
exit if( $. == 5 );
}
exit 0;
sub json
{
my ($data) = @_;
return decode_json($data);
}
sub json_parse
{
my ($data) = @_;
return json_to_perl($data);
}
sub json_xs
{
my ($data) = @_;
return $json_xs->decode($data);
}
sub json_xs_incremental
{
my ($data) = @_;
my $result = [];
$json_xs->incr_parse($data); # void context, so no parsing
push( @$result, $_ ) for( $json_xs->incr_parse );
return $result;
}
sub json_sl_incremental
{
my ($data) = @_;
my $result = [];
$json_sl->feed($data);
push( @$result, $_ ) for( $json_sl->fetch );
# ? error: JSON::SL - Got error CANT_INSERT at position 552 at json_to_perl.pl line 82, <$JSON> line 2.
return $result;
}