8

我将有一个可能非常大的 JSON 文件,我想从中流式传输而不是将其全部加载到内存中。根据来自 的以下陈述(我添加了重点)JSON::XS,我相信它不适合我的需要。是否有一个 Perl 5 JSON 模块可以从磁盘流式传输结果?

在某些情况下,需要对 JSON 文本进行增量解析。虽然此模块必须同时将 JSON 文本和生成的 Perl 数据结构同时保存在内存中,但它确实允许您以增量方式解析 JSON 流。它通过累积文本来做到这一点,直到它有一个完整的 JSON 对象,然后它可以对其进行解码。这个过程类似于使用 decode_prefix 来查看一个完整的 JSON 对象是否可用,但是效率更高(并且可以用最少的方法调用来实现)。

为了澄清,JSON 将包含一个对象数组。我想一次从文件中读取一个对象。

4

5 回答 5

13

在易用性和速度方面,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::SL0.2 秒和JSON::Streaming::Reader3.6 秒(请注意,JSON::SL一次输入 4k,我无法控制 JSON::Streaming::Reader 的缓冲区大小)。

于 2012-09-17T15:08:42.977 回答
3

您是否查看过在 search.cpan.org 上搜索“JSON Stream”时首先显示的JSON::Streaming::Reader ?

或者通过搜索“JSON SAX”找到JSON::SL - 不是很明显的搜索词,但您描述的内容听起来像是 XML 的 SAX 解析器。

于 2012-09-17T13:49:14.850 回答
2

它通过累积文本来做到这一点,直到它有一个完整的 JSON 对象,然后它可以对其进行解码。

这就是让你崩溃的原因。JSON 文档一个对象。

您需要更清楚地定义增量解析所需的内容。您是否正在寻找大型映射的一个元素?你想用你读/写的信息做什么?


我不知道任何库会通过一次从数组中读取一个元素来增量解析 JSON 数据。但是,使用有限状态自动机实现自己非常简单(基本上,您的文件具有格式\s*\[\s*([^,]+,)*([^,]+)?\s*\]\s*,除了您需要正确解析字符串中的逗号。)

于 2012-09-17T13:22:19.467 回答
2

您是否尝试先跳过右括号[,然后跳过逗号,

$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

于 2012-09-17T13:43:04.797 回答
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;
}
于 2013-06-14T10:29:26.747 回答