如何从文件中流式传输 JSON?
Posted
技术标签:
【中文标题】如何从文件中流式传输 JSON?【英文标题】:How can I stream JSON from a file? 【发布时间】:2012-09-09 17:11:44 【问题描述】:我将有一个可能非常大的 JSON 文件,我想从中流式传输而不是将其全部加载到内存中。根据JSON::XS
的以下声明(我添加了重点),我相信它不适合我的需求。是否有一个 Perl 5 JSON 模块可以从磁盘流式传输结果?
在某些情况下,需要对 JSON 文本进行增量解析。虽然此模块必须同时将 JSON 文本和生成的 Perl 数据结构同时保存在内存中,但它确实允许您以增量方式解析 JSON 流。它通过累积文本来做到这一点,直到它有一个完整的 JSON 对象,然后它可以对其进行解码。这个过程类似于使用 decode_prefix 来查看一个完整的 JSON 对象是否可用,但是效率更高(并且可以用最少的方法调用来实现)。
为了澄清,JSON 将包含一个对象数组。我想从文件中一次读取一个对象。
【问题讨论】:
我猜你有searched...你在找人担保模块吗?我的初步搜索将JSON::Streaming::Reader
和JSON::SL
列为潜在候选人,但不知道它们是否适合您的需求。
@Zaid 我只是在查看 JSON 模块,我没想过在名称中包含流(我错误地认为***狗会有这个功能)。跨度>
【参考方案1】:
如果您可以控制生成 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 FILE\n";
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;
【讨论】:
【参考方案2】:在易用性和速度方面,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->Valuen: $obj->Values\n";
__DATA__
[
"n": 0, "s": "zero" ,
"n": 1, "s": "one" ,
"n": 2, "s": "two"
]
JSON::Streaming::Reader
还可以,但它速度较慢并且接口过于冗长(所有这些 coderefs 都是必需的,即使许多什么都不做):
#!/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
0.2 秒和 JSON::Streaming::Reader
3.6 秒(注意,JSON::SL
一次输入 4k,我无法控制 JSON::Streaming::Reader 的缓冲区大小) .
【讨论】:
【参考方案3】:在 search.cpan.org 上搜索“JSON Stream”时,您是否查看过JSON::Streaming::Reader?
或者通过搜索“JSON SAX”找到JSON::SL - 不是很明显的搜索词,但你所描述的听起来像是 XML 的 SAX 解析器。
【讨论】:
我现在正在阅读 JSON::SL 的文档,接下来我将查看 JSON::Streaming::Reader。 作为 JSON::SL 的作者,它提供了一个类似 pull 的接口(如下例所示)和一个实际的类似 SAX 的接口(称为Tuba
)。它们是不同的,SAX 风格界面的缺点是显而易见的(复杂且缓慢..)【参考方案4】:
您是否尝试先跳过右括号[
,然后跳过逗号,
:
$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
【讨论】:
我还没有尝试过任何东西,文档似乎说它仍然将内容保存在内存中(即没有像XML::Twig
那样的刷新)。
其实第四个例子好像做你想做的事
看起来是这样,但看起来很糟糕。 CPAN 上显然还有其他专门的模块可能能够对我隐藏这些垃圾。我现在正在看它们。【参考方案5】:
它通过累积文本来做到这一点,直到它有一个完整的 JSON 对象,然后它可以对其进行解码。
这就是让你大吃一惊的原因。 JSON 文档是一个对象。
您需要更清楚地定义增量解析所需的内容。您是否正在寻找大型映射的一个元素?你想对你读/写的信息做什么?
我不知道任何库会通过一次从数组中读取一个元素来增量解析 JSON 数据。然而,使用有限状态自动机实现自己非常简单(基本上,您的文件格式为 \s*\[\s*([^,]+,)*([^,]+)?\s*\]\s*
,除了您需要正确解析字符串中的逗号。)
【讨论】:
我更新了问题,JSON 是一组对象,我一次想要一个对象。以上是关于如何从文件中流式传输 JSON?的主要内容,如果未能解决你的问题,请参考以下文章
将大型 json 文件从 Firebase 存储传输到 Firestore
如何在不中断流式传输作业的情况下更改 spark spark 流式事件中的 json 架构?