Pregunta ¿Cómo puedo transmitir JSON desde un archivo?


Tendré un archivo JSON posiblemente muy grande y quiero transmitirlo en lugar de cargarlo todo en la memoria. Basado en la siguiente declaración (agregué el énfasis) de JSON::XS, Creo que no se ajustará a mis necesidades. ¿Hay un módulo Perl 5 JSON que transmita los resultados desde el disco?

En algunos casos, existe la necesidad de un análisis incremental de textos JSON. Mientras este modulo siempre tiene que mantener el texto JSON y la estructura de datos resultante de Perl en la memoria al mismo tiempo, le permite analizar una secuencia JSON de forma incremental. Lo hace al acumular texto hasta que tenga un objeto JSON completo, que luego puede decodificar. Este proceso es similar al uso de decode_prefix para ver si un objeto JSON completo está disponible, pero es mucho más eficiente (y se puede implementar con un mínimo de llamadas a métodos).

Para aclarar, el JSON contendrá una matriz de objetos. Quiero leer un objeto a la vez del archivo.


5
2017-09-17 13:18


origen


Respuestas:


Has mirado JSON :: Streaming :: Reader que aparece como el primero al buscar 'JSON Stream' en search.cpan.org?

Alternativamente JSON :: SL se encuentra al buscar 'JSON SAX' - no es un término de búsqueda tan obvio, pero lo que describe suena como un analizador de SAX para XML.


3
2017-09-17 13:49



En términos de facilidad de uso y velocidad, JSON::SL parece ser el ganador

#!/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 estaba bien, pero es más lento y tiene una interfaz muy detallada (se requieren todos estos códigos, aunque muchos no hagan nada):

#!/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"  }
]

Para analizar 1.000 registros, tomó JSON::SL .2 segundos y JSON::Streaming::Reader 3,6 segundos (nota, JSON::SL estaba siendo alimentado 4k a la vez, no tenía control sobre el tamaño del buffer de JSON :: Streaming :: Reader).


11
2017-09-17 15:08



Lo hace al acumular texto hasta que tenga un objeto JSON completo, que luego puede decodificar.

Esto es lo que te molesta. Un documento json es un objeto

Necesita definir más claramente lo que quiere del análisis incremental. ¿Estás buscando un elemento de un mapeo grande? ¿Qué estás tratando de hacer con la información que lees / escribes?


No conozco ninguna biblioteca que analice de forma incremental datos JSON leyendo un elemento de una matriz a la vez. Sin embargo, esto es bastante simple de implementar usted mismo usando un autómata de estado finito (básicamente su archivo tiene el formato \s*\[\s*([^,]+,)*([^,]+)?\s*\]\s* excepto que necesita analizar comas en cadenas correctamente).


2
2017-09-17 13:22



¿Intentó omitir el primer bracket derecho? [ y luego las comas , :

$json->incr_text =~ s/^ \s* \[ //x;
...
$json->incr_text =~ s/^ \s* , //x;
...
$json->incr_text =~ s/^ \s* \] //x;

Como en el tercer ejemplo: http://search.cpan.org/dist/JSON-XS/XS.pm#EXAMPLES


2
2017-09-17 13:43



Si tiene control sobre cómo está generando su JSON, sugiero desactivar el formateo e imprimir un objeto por línea. Esto hace que el análisis sea simple, así:

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;
}

0