2011-08-01 19 views
11

Liên quan đến this questionthis answer (cho một câu hỏi khác) Tôi vẫn không thể xử lý UTF-8 bằng JSON.perl: Ngoại lệ không bắt buộc: ký tự UTF-8 không đúng định dạng trong chuỗi JSON

Tôi đã cố gắng đảm bảo tất cả yêu cầu bắt buộc dựa trên các đề xuất từ ​​các chuyên gia tốt nhất và theo như tôi thấy chuỗi có giá trị, được đánh dấu và gắn nhãn là UTF-8 càng tốt. Nhưng perl vẫn chết với một trong hai

Uncaught exception: malformed UTF-8 character in JSON string 

hoặc

Uncaught exception: Wide character in subroutine entry 

Tôi đang làm gì sai ở đây?

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

Chạy này cung cấp cho

(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 là từ http://training.perl.com/scripts/uniquote


Cập nhật:

Nhờ brian để làm nổi bật các giải pháp. Cập nhật nguồn để sử dụng json_text cho tất cả các chuỗi bình thường và json_bytes cho những gì đang xảy ra để được thông qua để JSON như sau đây hiện đang làm việc như mong đợi:

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

tôi phải nói rằng tôi nghĩ rằng các tài liệu cho các mô-đun JSON là cực kỳ không rõ ràng và một phần gây hiểu nhầm.

Cụm từ "văn bản" (ít nhất là với tôi) ngụ ý một chuỗi ký tự. Vì vậy, khi đọc $perl_scalar = decode_json $json_text Tôi có một mong đợi là của json_text là một chuỗi ký tự được mã hóa UTF-8. Đọc kỹ tài liệu, biết cần tìm gì, Bây giờ tôi thấy nó nói: "decode_json ... mong đợi một chuỗi UTF-8 (nhị phân) và cố gắng phân tích dưới dạng văn bản JSON được mã hóa UTF-8" Tuy nhiên, điều đó vẫn không rõ ràng theo ý kiến ​​của tôi.

Từ nền tảng của tôi sử dụng một ngôn ngữ có một số bổ sung phi ASCII nhân vật, tôi nhớ lại những ngày mà bạn đã phải đoán trang mã được sử dụng, email được sử dụng để chỉ làm tê liệt văn bản bằng cách tách các bit thứ 8 , vv Và "nhị phân" trong ngữ cảnh của chuỗi có nghĩa là một chuỗi chứa các ký tự bên ngoài miền ASCII 7 bit. Nhưng "nhị phân" thực sự là gì? Không phải tất cả các chuỗi nhị phân ở cấp độ cốt lõi phải không? Tài liệu cũng cho biết "giao diện đơn giản và nhanh chóng (mong đợi/tạo UTF-8)" và "xử lý unicode chính xác", điểm đầu tiên trong "Tính năng", cả hai mà không đề cập đến bất cứ nơi nào gần đó nó không muốn một chuỗi nhưng thay vào đó một chuỗi byte. Tôi sẽ yêu cầu tác giả ít nhất làm cho điều này rõ ràng hơn.

+0

Tiện ích Unicode của Tom cũng có sẵn dưới dạng [Unicode :: Tussle] (http://search.cpan.org/dist/Unicode-Tussle). –

Trả lời

12

Tôi mở rộng câu trả lời của mình trong Know the difference between character strings and UTF-8 strings.


Từ đọc tài liệu JSON, tôi nghĩ những chức năng đó không muốn chuỗi ký tự, nhưng đó là những gì bạn đang cố gắng cung cấp. Thay vào đó, họ muốn có một "chuỗi nhị phân UTF-8". Điều đó có vẻ kỳ quặc với tôi, nhưng tôi đoán rằng nó chủ yếu là để có đầu vào trực tiếp từ một tin nhắn HTTP thay vì một cái gì đó mà bạn gõ trực tiếp trong chương trình của bạn. Này hoạt động bởi vì tôi thực hiện một chuỗi byte đó là phiên bản được mã hóa UTF-8 của chuỗi của bạn:

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

Kết quả cho thấy rằng chuỗi ký tự không làm việc, nhưng các phiên bản chuỗi byte làm:

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

Vì vậy, nếu bạn lấy chuỗi ký tự của bạn, mà bạn gõ trực tiếp vào chương trình của bạn, và chuyển đổi nó thành một chuỗi byte UTF-8 mã hóa, nó hoạt động:

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

tôi nghĩ JSON nên đủ thông minh để đối phó với điều này, do đó bạn không cần phải suy nghĩ ở cấp độ này, nhưng đó là cách nó (cho đến nay).

+0

Re "Điều đó có vẻ lạ với tôi", nó không có vẻ lạ với tôi cả. Bạn không giải mã XML trước khi chuyển nó tới một trình phân tích cú pháp XML. Bạn không giải mã một chương trình Perl trước khi chuyển nó cho Perl. Nếu các trình phân tích cú pháp đó yêu cầu văn bản được giải mã, tệp sẽ phải được phân tích cú pháp hai lần: một lần để xác định mã hóa và một lần để thực hiện phân tích cú pháp thực tế. Hoàn toàn ngược lại, có vẻ rất lạ khi tôi giải mã một cái gì đó trước khi chuyển nó sang một phương thức gọi là 'decode'. – ikegami

+0

Re "Tôi nghĩ JSON nên đủ thông minh để giải quyết vấn đề này", điều đó là không thể. Nó không có cách nào để biết liệu một cái gì đó đã được giải mã hay chưa. – ikegami

+0

Nó có vẻ kỳ lạ với tôi bởi vì tôi đã không mong đợi nó. Tôi muốn mọi thứ chỉ hoạt động mà không có mức độ suy nghĩ này. Tại OSCON, kẹp chính của tôi là chúng ta chỉ có một vô hướng và chúng ta không thể biết đó là một chuỗi nhị phân hay một chuỗi ký tự. Đối với đủ thông minh, có lẽ nó là không thể, nhưng tôi vẫn muốn tính năng đó. Tôi tự hỏi điều gì đang xảy ra. Nếu Perl có nó được đánh dấu là một chuỗi UTF-8, JSON thực sự đang sử dụng cái gì? Tôi sẽ phải điều tra thêm. –

5

Các tài liệu nói

$perl_hash_or_arrayref = decode_json $utf8_encoded_json_text; 

nhưng bạn làm mọi thứ trong khả năng của mình để giải mã đầu vào trước khi đi qua nó để 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"); 
} 

Output:

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

PS — Nó sẽ được dễ dàng hơn để giúp bạn nếu bạn không có hai trang mã để chứng minh một vấn đề đơn giản.

-1

Tôi tin rằng tôi đã tình cờ gặp một câu trả lời!

  • những biểu tượng đẹp đến trong WebSocket và hoạt động tốt
  • JSON :: XS :: decode_json chết "nhân vật Wide"
  • không có lối thoát
  • (write_file đó json darn đi bonkers quá, tôi phải viết chức năng tăng tốc của riêng tôi)

Có rất nhiều yêu cầu DIY. Dưới đây là các lệnh IO của tôi:

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

Sau đó, để JSON decode thứ mà đi kèm trong các 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(); 

nào vừa ném cho tôi một vòng lặp - Tôi có thể cần ngửi muối!

Nhưng cách duy nhất tôi nhận được đường dẫn hoàn toàn bị xóa đối với các ký hiệu lạ khi di chuyển đĩa - perl - websocket/json - JS/HTML/codemirror/bất cứ điều gì và ngược lại. Các biểu tượng phải được ghi vào đĩa với mức tăng đột biến, với chế độ hoặc cấp độ utf8. Tôi đoán Mojo hoặc cái gì tôi đang sử dụng với nhau là phá vỡ rằng vì nó tất cả các công trình tốt trong một lớp lót perl, và tôi biết tôi có thể sửa chữa tất cả, tôi chỉ để goshdarn bận rộn.

Có lẽ có điều gì đó đơn giản ở đâu đó nhưng tôi nghi ngờ điều đó. Cuộc sống chỉ áp đảo tôi đôi khi, tôi tuyên bố!

Một chút điên rồ hơn kết quả là các ký tự bị hỏng trên đĩa nhưng ký tự làm việc trong perl và ở đầu kia của websocket.

Các vấn đề liên quan