2011-08-01 8 views
11

relacionado con this question y this answer (a otra pregunta) Todavía no puedo procesar UTF-8 con JSON.perl: excepción no detectada: carácter UTF-8 malformado en cadena JSON

He intentado asegurarme de que se invoque todo el vudú requerido de acuerdo con las recomendaciones de los mejores expertos, y hasta donde puedo ver, la cadena es tan válida, marcada y etiquetada como UTF-8 como sea posible. Pero aún así Perl muere, ya sea con

Uncaught exception: malformed UTF-8 character in JSON string 

o

Uncaught exception: Wide character in subroutine entry 

¿qué estoy haciendo mal aquí?

(hlovdal) localhost:/work/2011/perl_unicode>cat json_malformed_utf8.pl 
#!/usr/bin/perl -w -CSAD 

### BEGIN ### 
# Apparently the very best perl unicode boiler template code that exist, 
# https://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/6163129#6163129 
# Slightly modified. 

use v5.12; # minimal for unicode string feature 
#use v5.14; # optimal for unicode string feature 

use utf8;             # Declare that this source unit is encoded as UTF‑8. Although 
                  # once upon a time this pragma did other things, it now serves 
                  # this one singular purpose alone and no other. 
use strict; 
use autodie; 

use warnings;            # Enable warnings, since the previous declaration only enables 
use warnings qw< FATAL utf8  >;     # strictures and features, not warnings. I also suggest 
                  # promoting Unicode warnings into exceptions, so use both 
                  # these lines, not just one of them. 

use open  qw(:encoding(UTF-8) :std);    # Declare that anything that opens a filehandles within this 
                  # lexical scope but not elsewhere is to assume that that 
                  # stream is encoded in UTF‑8 unless you tell it otherwise. 
                  # That way you do not affect other module’s or other program’s code. 

use charnames qw<:full>;        # Enable named characters via \N{CHARNAME}. 
use feature  qw<unicode_strings>; 

use Carp    qw< carp croak confess cluck >; 
use Encode    qw< encode decode >; 
use Unicode::Normalize qw< NFD NFC >; 

END { close STDOUT } 

if (grep /\P{ASCII}/ => @ARGV) { 
    @ARGV = map { decode("UTF-8", $_) } @ARGV; 
} 

$| = 1; 

binmode(DATA, ":encoding(UTF-8)");      # If you have a DATA handle, you must explicitly set its encoding. 

# give a full stack dump on any untrapped exceptions 
local $SIG{__DIE__} = sub { 
    confess "Uncaught exception: @_" unless $^S; 
}; 

# now promote run-time warnings into stackdumped exceptions 
# *unless* we're in an try block, in which 
# case just generate a clucking stackdump instead 
local $SIG{__WARN__} = sub { 
    if ($^S) { cluck "Trapped warning: @_" } 
    else  { confess "Deadly warning: @_" } 
}; 

### END ### 


use JSON; 
use Encode; 

use Getopt::Long; 
use Encode; 

my $use_nfd = 0; 
my $use_water = 0; 
GetOptions("nfd" => \$use_nfd, "water" => \$use_water); 

print "JSON->backend->is_pp = ", JSON->backend->is_pp, ", JSON->backend->is_xs = ", JSON->backend->is_xs, "\n"; 

sub check { 
     my $text = shift; 
     return "is_utf8(): " . (Encode::is_utf8($text) ? "1" : "0") . ", is_utf8(1): " . (Encode::is_utf8($text, 1) ? "1" : "0"). ". "; 
} 

my $json_text = "{ \"my_test\" : \"hei på deg\" }\n"; 
if ($use_water) { 
     $json_text = "{ \"water\" : \"水\" }\n"; 
} 
if ($use_nfd) { 
     $json_text = NFD($json_text); 
} 

print check($json_text), "\$json_text = $json_text"; 

# test from perluniintro(1) 
if (eval { decode_utf8($json_text, Encode::FB_CROAK); 1 }) { 
     print "string is valid utf8\n"; 
} else { 
     print "string is not valid utf8\n"; 
} 

my $hash_ref1 = JSON->new->utf8->decode($json_text); 
my $hash_ref2 = decode_json($json_text); 

__END__ 

La ejecución de este da

(hlovdal) localhost:/work/2011/perl_unicode>./json_malformed_utf8.pl 
JSON->backend->is_pp = 0, JSON->backend->is_xs = 1 
is_utf8(): 1, is_utf8(1): 1. $json_text = { "my_test" : "hei på deg" } 
string is valid utf8 
Uncaught exception: malformed UTF-8 character in JSON string, at character offset 20 (before "\x{5824}eg" }\n") at ./json_malformed_utf8.pl line 96. 
at ./json_malformed_utf8.pl line 46 
     main::__ANON__('malformed UTF-8 character in JSON string, at character offset...') called at ./json_malformed_utf8.pl line 96 
(hlovdal) localhost:/work/2011/perl_unicode>./json_malformed_utf8.pl | ./uniquote 
Uncaught exception: malformed UTF-8 character in JSON string, at character offset 20 (before "\x{5824}eg" }\n") at ./json_malformed_utf8.pl line 96. 
at ./json_malformed_utf8.pl line 46 
     main::__ANON__('malformed UTF-8 character in JSON string, at character offset...') called at ./json_malformed_utf8.pl line 96 
JSON->backend->is_pp = 0, JSON->backend->is_xs = 1 
is_utf8(): 1, is_utf8(1): 1. $json_text = { "my_test" : "hei p\N{U+E5} deg" } 
string is valid utf8 
(hlovdal) localhost:/work/2011/perl_unicode>./json_malformed_utf8.pl -nfd | ./uniquote 
Uncaught exception: Wide character in subroutine entry at ./json_malformed_utf8.pl line 96. 
at ./json_malformed_utf8.pl line 46 
     main::__ANON__('Wide character in subroutine entry at ./json_malformed_utf8.pl line 96.\x{a}') called at ./json_malformed_utf8.pl line 96 
JSON->backend->is_pp = 0, JSON->backend->is_xs = 1 
is_utf8(): 1, is_utf8(1): 1. $json_text = { "my_test" : "hei pa\N{U+30A} deg" } 
string is valid utf8 
(hlovdal) localhost:/work/2011/perl_unicode>./json_malformed_utf8.pl -water 
JSON->backend->is_pp = 0, JSON->backend->is_xs = 1 
is_utf8(): 1, is_utf8(1): 1. $json_text = { "water" : "水" } 
string is valid utf8 
Uncaught exception: Wide character in subroutine entry at ./json_malformed_utf8.pl line 96. 
at ./json_malformed_utf8.pl line 46 
     main::__ANON__('Wide character in subroutine entry at ./json_malformed_utf8.pl line 96.\x{a}') called at ./json_malformed_utf8.pl line 96 
(hlovdal) localhost:/work/2011/perl_unicode>./json_malformed_utf8.pl -water | ./uniquote 
Uncaught exception: Wide character in subroutine entry at ./json_malformed_utf8.pl line 96. 
at ./json_malformed_utf8.pl line 46 
     main::__ANON__('Wide character in subroutine entry at ./json_malformed_utf8.pl line 96.\x{a}') called at ./json_malformed_utf8.pl line 96 
JSON->backend->is_pp = 0, JSON->backend->is_xs = 1 
is_utf8(): 1, is_utf8(1): 1. $json_text = { "water" : "\N{U+6C34}" } 
string is valid utf8 
(hlovdal) localhost:/work/2011/perl_unicode>./json_malformed_utf8.pl -water --nfd | ./uniquote 
Uncaught exception: Wide character in subroutine entry at ./json_malformed_utf8.pl line 96. 
at ./json_malformed_utf8.pl line 46 
     main::__ANON__('Wide character in subroutine entry at ./json_malformed_utf8.pl line 96.\x{a}') called at ./json_malformed_utf8.pl line 96 
JSON->backend->is_pp = 0, JSON->backend->is_xs = 1 
is_utf8(): 1, is_utf8(1): 1. $json_text = { "water" : "\N{U+6C34}" } 
string is valid utf8 
(hlovdal) localhost:/work/2011/perl_unicode>rpm -q perl perl-JSON perl-JSON-XS 
perl-5.12.4-159.fc15.x86_64 
perl-JSON-2.51-1.fc15.noarch 
perl-JSON-XS-2.30-2.fc15.x86_64 
(hlovdal) localhost:/work/2011/perl_unicode> 

uniquote es de http://training.perl.com/scripts/uniquote


Actualización:

Gracias a Brian para poner de relieve la solución. Actualización de la fuente para utilizar json_text para todas las cadenas normales y json_bytes para lo que va a pasar a JSON como el ahora siguiendo funciona como se esperaba:

my $json_bytes = encode('UTF-8', $json_text); 
my $hash_ref1 = JSON->new->utf8->decode($json_bytes); 

hay que decir que creo que la documentación del módulo JSON es extremadamente poco claro y parcialmente engañoso.

La frase "texto" (al menos para mí) implica una cadena de caracteres. Al leer $perl_scalar = decode_json $json_text tengo una expectativa de de que json_text sea una cadena de caracteres codificada en UTF-8. Volviendo a leer a fondo la documentación, sabiendo qué buscar, Ahora veo que dice: "decode_json ... espera una cadena UTF-8 (binaria) e intenta analizar que como un texto JSON codificado en UTF-8" Sin embargo, eso todavía no está claro en mi opinión.

Desde mi experiencia utilizando un lenguaje de tener algún no-ASCII adicional caracteres, recuerdo en los días en los que tuvo que adivinar la página de códigos siendo utilizados, correo electrónico utilizada para simplemente paralizar el texto mediante la eliminación de la octava poco , etc. Y "binario" en el contexto de cadenas significa una cadena que contiene caracteres fuera del dominio ASCII de 7 bits. Pero, ¿qué es "binario" realmente? ¿No son todas las cadenas binarias en el nivel central?

La documentación también dice "interfaces simples y rápidas (esperar/generar UTF-8)" y "corregir el manejo de Unicode", primer punto en "Características", sin mencionar en ninguna parte que no quiere una cadena pero en su lugar una secuencia de bytes Pediré al autor que al menos lo aclare.

+0

Las utilidades Unicode de Tom también están disponibles como [Unicode :: Tussle] (http://search.cpan.org/dist/Unicode-Tussle). –

Respuesta

12

Amplío mi respuesta en Know the difference between character strings and UTF-8 strings.


De la lectura de los documentos JSON, creo que esas funciones no quieren una cadena de caracteres, pero eso es lo que estamos tratando de darle. En su lugar, quieren una "cadena binaria UTF-8". Eso me parece extraño, pero supongo que es principalmente para tomar la información directamente de un mensaje HTTP en lugar de algo que escriba directamente en su programa. Esto funciona porque hago una cadena de bytes que es la versión codificada en UTF-8 de la cadena:

use v5.14; 

use utf8;             
use warnings;            
use feature  qw<unicode_strings>; 

use Data::Dumper; 
use Devel::Peek; 
use JSON; 

my $filename = 'hei.txt'; 
my $char_string = qq({ "my_test" : "hei på deg" }); 
open my $fh, '>:encoding(UTF-8)', $filename; 
print $fh $char_string; 
close $fh; 


{ 
say '=' x 70; 
my $byte_string = qq({ "my_test" : "hei p\303\245 deg" }); 
print "Byte string peek:------\n"; Dump($byte_string); 
decode($byte_string); 
} 


{ 
say '=' x 70; 
my $raw_string = do { 
    open my $fh, '<:raw', $filename; 
    local $/; <$fh>; 
    }; 
print "raw string peek:------\n"; Dump($raw_string); 

decode($raw_string); 
} 

{ 
say '=' x 70; 
my $char_string = do { 
    open my $fh, '<:encoding(UTF-8)', $filename; 
    local $/; <$fh>; 
    }; 
print "char string peek:------\n"; Dump($char_string); 

decode($char_string); 
} 

sub decode { 
    my $string = shift; 

    my $hash_ref2 = eval { decode_json($string) }; 
    say "Error in sub form: [email protected]" if [email protected]; 
    print Dumper($hash_ref2); 

    my $hash_ref1 = eval { JSON->new->utf8->decode($string) }; 
    say "Error in method form: [email protected]" if [email protected]; 
    print Dumper($hash_ref1); 
    } 

El resultado muestra que la cadena de caracteres no funciona, pero las versiones de cadena de bytes hacer:

====================================================================== 
Byte string peek:------ 
SV = PV(0x100801190) at 0x10089d690 
    REFCNT = 1 
    FLAGS = (PADMY,POK,pPOK) 
    PV = 0x100209890 " { \"my_test\" : \"hei p\303\245 deg\" } "\0 
    CUR = 31 
    LEN = 32 
$VAR1 = { 
      'my_test' => "hei p\x{e5} deg" 
     }; 
$VAR1 = { 
      'my_test' => "hei p\x{e5} deg" 
     }; 
====================================================================== 
raw string peek:------ 
SV = PV(0x100839240) at 0x10089d780 
    REFCNT = 1 
    FLAGS = (PADMY,POK,pPOK) 
    PV = 0x100212260 " { \"my_test\" : \"hei p\303\245 deg\" } "\0 
    CUR = 31 
    LEN = 32 
$VAR1 = { 
      'my_test' => "hei p\x{e5} deg" 
     }; 
$VAR1 = { 
      'my_test' => "hei p\x{e5} deg" 
     }; 
====================================================================== 
char string peek:------ 
SV = PV(0x10088f3b0) at 0x10089d840 
    REFCNT = 1 
    FLAGS = (PADMY,POK,pPOK,UTF8) 
    PV = 0x1002017b0 " { \"my_test\" : \"hei p\303\245 deg\" } "\0 [UTF8 " { "my_test" : "hei p\x{e5} deg" } "] 
    CUR = 31 
    LEN = 32 
Error in sub form: malformed UTF-8 character in JSON string, at character offset 21 (before "\x{5824}eg" } ") at utf-8.pl line 51. 

$VAR1 = undef; 
Error in method form: malformed UTF-8 character in JSON string, at character offset 21 (before "\x{5824}eg" } ") at utf-8.pl line 55. 

$VAR1 = undef; 

lo tanto, si usted toma su cadena de caracteres, que escribió directamente en su programa, y ​​lo convierten en una cadena de bytes codificación UTF-8, funciona:

use v5.14; 

use utf8;             
use warnings;            
use feature  qw<unicode_strings>; 

use Data::Dumper; 
use Encode qw(encode_utf8); 
use JSON; 

my $char_string = qq({ "my_test" : "hei på deg" }); 

my $string = encode_utf8($char_string); 

decode($string); 

sub decode { 
    my $string = shift; 

    my $hash_ref2 = eval { decode_json($string) }; 
    say "Error in sub form: [email protected]" if [email protected]; 
    print Dumper($hash_ref2); 

    my $hash_ref1 = eval { JSON->new->utf8->decode($string) }; 
    say "Error in method form: [email protected]" if [email protected]; 
    print Dumper($hash_ref1); 
    } 

Creo JSON debe ser lo suficientemente inteligente lidiar con esto para no tener que pensar en este nivel, pero así son las cosas (hasta ahora).

+0

Re "Eso me parece extraño", no me parece extraño en absoluto. No decodifica XML antes de pasarlo a un analizador XML. No decodifica un programa Perl antes de pasarlo a Perl. Si esos analizadores requerían texto decodificado, el archivo debería analizarse dos veces: una para determinar la codificación y otra para el análisis real. Todo lo contrario, me parece muy extraño decodificar algo antes de pasarlo a un método llamado 'decodificar'. – ikegami

+0

Re "Creo que JSON debe ser lo suficientemente inteligente como para hacer frente a esto", es imposible. No tiene forma de saber si algo ya ha sido decodificado o no. – ikegami

+0

Me parece extraño porque no lo esperaba. Me gustaría que las cosas simplemente funcionen sin este nivel de pensamiento. En OSCON, mi principal queja fue que solo tenemos un escalar y no podemos decir si es una cadena binaria o una cadena de caracteres. En cuanto a ser lo suficientemente inteligente, tal vez es imposible, pero aún me gustaría esa característica. Me pregunto qué está pasando sin embargo. Si Perl lo tiene marcado como una cadena UTF-8, ¿qué está realmente usando JSON? Tendré que investigar más. –

5

Los documentos dicen

$perl_hash_or_arrayref = decode_json $utf8_encoded_json_text; 

sin embargo, que hacen todo en su poder para decodificar la entrada antes de pasarla a decode_json.

use strict; 
use warnings; 
use utf8; 

use Data::Dumper qw(Dumper); 
use Encode  qw(encode); 
use JSON   qw(); 

for my $json_text (
    qq{{ "my_test" : "hei på deg" }\n}, 
    qq{{ "water" : "水" }\n}, 
) { 
    my $json_utf8 = encode('UTF-8', $json_text); # Counteract "use utf8;" 
    my $data = JSON->new->utf8->decode($json_utf8); 

    local $Data::Dumper::Useqq = 1; 
    local $Data::Dumper::Terse = 1; 
    local $Data::Dumper::Indent = 0; 
    print(Dumper($data), "\n"); 
} 

Salida:

{"my_test" => "hei p\x{e5} deg"} 
{"water" => "\x{6c34}"} 

PS — Sería más fácil para ayudarle si usted no tenía dos páginas de código para demostrar un problema sencillo.

-1

¡Creo que he tenido una respuesta casual!

  • símbolos bonitas vienen en la WebSocket y funcionan bien
  • JSON :: :: XS decode_json muere "caracteres anchos"
  • hay salida
  • (write_file de ese maldito JSON va loco también, tuve que escribir mi propia función de impulso)

Se necesitan muchos bricolaje. Aquí están los comandos de mi IO:

sub spurt { 
my $self = shift; 
my $file = shift; 
my $stuff = shift; 
say "Hostinfo: spurting $file (".length($stuff).")"; 
    open my $f, '>', $file || die "O no $!"; 
binmode $f, ':utf8'; 
print $f $stuff."\n"; 
#         slurp instead does: 
#         my $m = join "", <$f>; 
close $f; 
} 

Entonces a JSON cosas decodificación que viene en el WebSocket:

start_timer(); 
    $hostinfo->spurt('/tmp/elvis', $msg); 
    my $convert = q{perl -e 'use YAML::Syck; use JSON::XS; use File::Slurp;} 
    .q{print " - reading json from /tmp/elvis\n";} 
    .q{my $j = read_file("/tmp/elvis");} 
    .q{print "! json already yaml !~?\n$j\n" if $j =~ /^---/s;} 
    .q{print " - convert json -> yaml\n";} 
    .q{my $d = decode_json($j);} 
    .q{print " - write yaml to /tmp/elvis\n";} 
    .q{DumpFile("/tmp/elvis", $d);} 
    .q{print " - done\n";} 
    .q{'}; 
    `$convert`; 

    eval { 
    $j = LoadFile('/tmp/elvis'); 

    while (my ($k, $v) = each %$j) { 
     if (ref \$v eq "SCALAR") { 
      $j->{$k} = Encode::decode_utf8($v); 
     } 
    } 
    }; 
    say "Decode in ".show_delta(); 

Lo cual me ha lanzado para un bucle - que podría necesitar sales de olor!

Pero la única forma en que obtuve la ruta fue completamente borrada para símbolos extraños que viajaban en el disco - perl - websocket/json - JS/HTML/codemirror/lo que sea y viceversa. Los símbolos se deben escribir en el disco con chorro, con el: nivel o modo utf8. Creo que Mojo o algo que estoy usando juntos está rompiendo eso, ya que todo funciona bien en un trazador de líneas de Perl, y sé que puedo arreglarlo todo, estoy tan ocupado.

Probablemente haya algo simple en alguna parte, pero lo dudo. ¡La vida me agobia a veces, lo declaro!

Una locura menos que esta da como resultado caracteres rotos en el disco pero caracteres en perl y en el otro extremo del websocket.

Cuestiones relacionadas