【问题标题】:How can I stream JSON from a file?如何从文件中流式传输 JSON?
【发布时间】:2012-09-09 17:11:44
【问题描述】:

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

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

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

【问题讨论】:

  • 我猜你有searched...你在找人担保模块吗?我的初步搜索将JSON::Streaming::ReaderJSON::SL 列为潜在候选人,但不知道它们是否适合您的需求。
  • @Zaid 我只是在查看 JSON 模块,我没想过在名称中包含流(我错误地认为顶级狗会有这个功能)。跨度>

标签: json perl stream


【解决方案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->{Value}{n}: $obj->{Value}{s}\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 是一组对象,我一次想要一个对象。
      猜你喜欢
      • 1970-01-01
      • 2016-01-21
      • 1970-01-01
      • 2019-07-03
      • 1970-01-01
      • 1970-01-01
      • 2017-11-21
      • 2013-05-16
      • 2016-07-28
      相关资源
      最近更新 更多