如何从文件中流式传输 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::ReaderJSON::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 架构?

如何使用 createWriteStream 将 JSON 流式传输到 BigQuery 表?

从 Internet 流式传输单个音频文件

如何流式传输从 FTP 获取的文件而不将其存储在本地?

如何将 JSON 数组从 NodeJS 流式传输到 postgres