perl: Uncaught исключение: искаженный символ UTF-8 в строке JSON - PullRequest
11 голосов
/ 02 августа 2011

Относительно этого вопроса и этого ответа (к другому вопросу) Я все еще не могу обработать UTF-8 с JSON.

Я пытался сделатьуверен, что все необходимые voodoo вызываются на основе рекомендаций самых лучших экспертов, и, насколько я вижу, строка является максимально допустимой, помеченной и помеченной как UTF-8, насколько это возможно.Но все равно Perl умирает с

Uncaught exception: malformed UTF-8 character in JSON string

или

Uncaught exception: Wide character in subroutine entry

Что я здесь не так делаю?

(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__

Запуск этого дает

(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 от http://training.perl.com/scripts/uniquote


Обновление:

Спасибо Брайану за выделение решения.Обновление источника для использования json_text для всех обычных строк и json_bytes для того, что будет передаваться в JSON, как показано ниже, теперь работает, как ожидалось:

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

Я должен сказать, что я думаю, что документациядля модуля JSON крайне непонятно и частично вводит в заблуждение.

Фраза «текст» (по крайней мере для меня) подразумевает строку символов.Поэтому при чтении $perl_scalar = decode_json $json_text я ожидаю, что json_text будет строкой символов в кодировке UTF-8.Тщательно перечитывая документацию, зная, что искать, я теперь вижу, что там написано: «decode_json ... ожидает строку UTF-8 (двоичную) и пытается проанализировать ее как текст JSON в кодировке UTF-8», однаковсе еще неясно, на мой взгляд.

Исходя из моего опыта использования языка с некоторыми дополнительными символами, не входящими в ASCII, я помню, что в те времена, когда приходилось угадывать используемую кодовую страницу, электронная почта использовалась лишь для того, чтобы наносить вредтекст путем удаления 8-го бита и т. д. И «двоичный» в контексте строк означает строку, содержащую символы вне 7-битного домена ASCII.Но что такое «бинарный» на самом деле?Разве все строки не являются двоичными на уровне ядра?

В документации также говорится "простые и быстрые интерфейсы (ожидайте / генерируйте UTF-8)" и "правильная обработка Юникода", первая точка в разделе "Функции", обене упоминая где-либо рядом, что он не хочет строку, а вместо этого последовательность байтов.Я попрошу автора сделать это, по крайней мере, более ясным.

Ответы [ 3 ]

13 голосов
/ 02 августа 2011

Я расширяю свой ответ в Знайте разницу между строками символов и строками UTF-8 .


Из прочтения документации JSON я думаю, что эти функции не хотят символьную строку, но вы пытаетесь ее передать. Вместо этого они хотят "двоичную строку UTF-8". Это кажется странным для меня, но я предполагаю, что это в основном берет ввод непосредственно из сообщения HTTP вместо того, что вы вводите непосредственно в вашей программе. Это работает, потому что я делаю байтовую строку, которая является версией вашей строки в кодировке UTF-8:

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: $@" if $@;
    print Dumper( $hash_ref2 );

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

Вывод показывает, что строка символов не работает, но версии байтовых строк работают:

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

Итак, если вы возьмете строку символов, которую вы ввели непосредственно в вашу программу, и преобразуете ее в строку байтов в кодировке UTF-8, это сработает:

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: $@" if $@;
    print Dumper( $hash_ref2 );

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

Я думаю, JSON должен быть достаточно умен, чтобы справиться с этим, чтобы вам не приходилось думать на этом уровне, но так оно и есть (пока).

7 голосов
/ 02 августа 2011

В документах написано

$perl_hash_or_arrayref  = decode_json $utf8_encoded_json_text;

все же вы делаете все, что в ваших силах, для декодирования ввода перед передачей его в 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");
}

Выход:

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

PS & mdash; Вам было бы легче помочь, если бы у вас не было двух страниц кода для демонстрации простой проблемы.

0 голосов
/ 15 июня 2014

Я полагаю, что я случайно наткнулся на ответ!

  • красивые символы входят в websocket и работают нормально
  • JSON :: XS :: decode_json dies "Широкий символ"
  • нет выхода
  • (write_file этого json darn тоже помешан, мне пришлось написать свою собственную функцию рывка)

Требуется много DIY,Вот мои команды ввода-вывода:

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

Затем для JSON-декодирования материала, который входит в веб-сокет:

    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();

, который только что бросил меня в цикл - мне могут понадобиться нюхательные соли!

Но единственный способ, которым я полностью очистил путь для странных символов, путешествующих по диску - perl - websocket / json - JS / HTML / codemirror / любой и обратно.Символы должны быть записаны на диск спуртом с уровнем или режимом utf8.Я думаю, что Mojo или что-то, что я использую вместе, ломает это, поскольку все это прекрасно работает в perl one liner, и я знаю, что могу все это исправить, я просто так занят goshdarn.

Возможно, что-то естьпросто где-то, но я сомневаюсь в этом.Я иногда заявляю, что жизнь просто переполняет меня!

Одно меньшее безумие, чем это, приводит к поломке символов на диске, но к работе символов в perl и на другом конце веб-сокета.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...