Perl: исключение uncaught: неправильный символ UTF-8 в строке JSON
Связанный с этот вопрос и этот ответ (на другой вопрос), я все еще не могу обрабатывать UTF -8 с JSON.
Я попытался удостовериться, что все необходимые вуду вызваны на основе рекомендаций от лучших экспертов, и насколько я вижу, строка верна, обозначена и помечена как 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,
# /info/14109/why-does-modern-perl-avoid-utf-8-by-default/102476#102476
# 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
Update:
Спасибо brian за выделение решения. Обновление источника для использования 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)" и "правильная обработка Юникода", первая точка в разделе "Возможности", причем без упоминания где-либо рядом, что она не хочет строки, байтовой последовательности. Я попрошу
автор, чтобы хотя бы сделать это более ясным.
Ответы
Ответ 1
Я расширяю свой ответ в Знайте разницу между символьными строками и строками 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: [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 );
}
Результат показывает, что строка символов не работает, но версии строки байтов:
======================================================================
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: [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 );
}
Я думаю, что JSON должен быть достаточно умным, чтобы справиться с этим, поэтому вам не нужно думать на этом уровне, но так, как это (пока).
Ответ 2
Документы говорят
$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 — Было бы проще помочь вам, если бы у вас не было двух страниц кода, чтобы продемонстрировать простую проблему.
Ответ 3
Я считаю, что случайно случилось через ответ!
- симпатичные символы входят в websocket и работают отлично
- JSON:: XS:: decode_json умирает "Широкий символ"
- нет выхода
- (write_file этого json darn тоже идет, я должен был написать свою собственную функцию spurt)
Там нужно много DIY. Вот мои команды 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;
}
Затем JSON расшифровывает материал, который входит в 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();
Который только что бросил меня на петлю - мне, возможно, понадобятся пахнущие соли!
Но единственный способ, которым я получил путь, полностью очищенный для странных символов, перемещающих диск - perl - websocket/json - JS/HTML/codemirror/безотносительно и обратно. Символы должны быть записаны на диск с рывком с помощью: utf8 уровня или режима. Я предполагаю, что Mojo или что-то, что я использую вместе, ломается, потому что все отлично работает в perl-лайнере, и я знаю, что могу исправить все это, я просто так занят goshdarn.
Там, вероятно, что-то простое, но я в этом сомневаюсь. Жизнь меня просто переполняет, я заявляю!
Одно менее безумие, чем это, приводит к сломанным символам на диске, но рабочим символам в perl и на другом конце websocket.