Проблема с декодированием UTF-8 JSON в Perl - PullRequest
4 голосов
/ 01 июля 2011

Символы UTF-8 уничтожаются при обработке библиотекой JSON (возможно, это похоже на Проблема с декодированием Unicode JSON в perl , однако установка binmode создает только другую проблему).

Я сократил проблему до следующего примера:

(hlovdal) localhost:/tmp/my_test>cat my_test.pl
#!/usr/bin/perl -w
use strict;
use warnings;
use JSON;
use File::Slurp;
use Getopt::Long;
use Encode;

my $set_binmode = 0;
GetOptions("set-binmode" => \$set_binmode);

if ($set_binmode) {
        binmode(STDIN,  ":encoding(UTF-8)");
        binmode(STDOUT, ":encoding(UTF-8)");
        binmode(STDERR, ":encoding(UTF-8)");
}

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 $my_test = "hei på deg";
my $json_text = read_file('my_test.json');
my $hash_ref = JSON->new->utf8->decode($json_text);

print check($my_test), "\$my_test = $my_test\n";
print check($json_text), "\$json_text = $json_text";
print check($$hash_ref{'my_test'}), "\$\$hash_ref{'my_test'} = " . $$hash_ref{'my_test'} .  "\n";

(hlovdal) localhost:/tmp/my_test>

При запуске тестирования текст почему-то записывается в iso-8859-1. Установка типа binmode решает его, но затем вызывает двойное кодирование других строк.

(hlovdal) localhost:/tmp/my_test>cat my_test.json 
{ "my_test" : "hei på deg" }
(hlovdal) localhost:/tmp/my_test>file my_test.json 
my_test.json: UTF-8 Unicode text
(hlovdal) localhost:/tmp/my_test>hexdump -c my_test.json 
0000000   {       "   m   y   _   t   e   s   t   "       :       "   h
0000010   e   i       p 303 245       d   e   g   "       }  \n        
000001e
(hlovdal) localhost:/tmp/my_test>
(hlovdal) localhost:/tmp/my_test>perl my_test.pl
is_utf8(): 0, is_utf8(1): 0. $my_test = hei på deg
is_utf8(): 0, is_utf8(1): 0. $json_text = { "my_test" : "hei på deg" }
is_utf8(): 1, is_utf8(1): 1. $$hash_ref{'my_test'} = hei p� deg
(hlovdal) localhost:/tmp/my_test>perl my_test.pl --set-binmode
is_utf8(): 0, is_utf8(1): 0. $my_test = hei på deg
is_utf8(): 0, is_utf8(1): 0. $json_text = { "my_test" : "hei på deg" }
is_utf8(): 1, is_utf8(1): 1. $$hash_ref{'my_test'} = hei på deg
(hlovdal) localhost:/tmp/my_test>

Что вызывает это и как решить?


Это на недавно установленной и обновленной системе Fedora 15.

(hlovdal) localhost:/tmp/my_test>perl --version | grep version
This is perl 5, version 12, subversion 4 (v5.12.4) built for x86_64-linux-thread-multi
(hlovdal) localhost:/tmp/my_test>rpm -q perl-JSON
perl-JSON-2.51-1.fc15.noarch
(hlovdal) localhost:/tmp/my_test>locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
(hlovdal) localhost:/tmp/my_test>

Обновление: добавление use utf8 не решает проблему, символы по-прежнему не обрабатываются правильно (хотя и немного отличаются от ранее):

(hlovdal) localhost:/tmp/my_test>perl  my_test.pl
is_utf8(): 1, is_utf8(1): 1. $my_test = hei p� deg
is_utf8(): 0, is_utf8(1): 0. $json_text = { "my_test" : "hei på deg" }
is_utf8(): 1, is_utf8(1): 1. $$hash_ref{'my_test'} = hei p� deg
(hlovdal) localhost:/tmp/my_test>perl  my_test.pl --set-binmode
is_utf8(): 1, is_utf8(1): 1. $my_test = hei på deg
is_utf8(): 0, is_utf8(1): 0. $json_text = { "my_test" : "hei på deg" }
is_utf8(): 1, is_utf8(1): 1. $$hash_ref{'my_test'} = hei på deg
(hlovdal) localhost:/tmp/my_test>

Как отмечено perlunifaq

Можно ли использовать Unicode в моих источниках Perl?

Да, вы можете! Если ваши источники UTF-8, вы можете указать, что с использованием utf8 прагмы.

use utf8;

Это ничего не значит для вашего вход или к вашему выводу. Это только влияет на то, как ваши источники читать. Вы можете использовать Unicode в строке литералы, в идентификаторах (но они все еще должны быть "символы слова" в соответствии с \ ш) и даже на заказ разделители.

Ответы [ 3 ]

9 голосов
/ 01 июля 2011

Вы сохранили свою программу в UTF-8, но забыли сообщить Perl.Добавить use utf8;.

Кроме того, вы программируете слишком сложно.JSON функционирует в формате DWYM.Чтобы проверить вещи, используйте Devel :: Peek.

use utf8; # for the following line
my $my_test = 'hei på deg';

use Devel::Peek qw(Dump);
use File::Slurp (read_file);
use JSON qw(decode_json);

my $hash_ref = decode_json(read_file('my_test.json'));

Dump $hash_ref; # Perl character strings
Dump $my_test;  # Perl character string
0 голосов
/ 18 июля 2012

Это только мое впечатление, или эта библиотека perl ожидает, что вы запишите байт-код UTF-8 в строку isoLatin1 (флаг utf-8 отключен в строке); И также, он возвращает вам байт-код UTF-8 в iso латинской строке:

#! /usr/bin/perl -w
use strict;
use Encode;
use Data::Dumper qw(Dumper);
use JSON; # imports encode_json, decode_json, to_json and from_json.
use utf8;

###############
## EXAMPLE 1:
################
my $json = JSON->new->allow_nonref;
my $exampleAJsonObj = { key1 => 'a'};
my $exampleAText = $json->utf8->encode( $exampleAJsonObj ); 
my $exampleAJsonObfUtf = { key1 => 'ä'};
my $exampleATextUtf = $json->utf8->encode( $exampleAJsonObfUtf); 


#binmode(STDOUT, ":utf8");
print "EXAMPLE1: ";
print "\n";
print encode 'UTF-8', "exampleAText: $exampleAText and as object: " . Dumper($exampleAJsonObj);
print "\n";
print encode 'UTF-8', "exampleATextUtf: $exampleATextUtf and as object: " . Dumper($exampleAJsonObfUtf) . " Key1 was: " . $exampleAJsonObfUtf->{key1};
print "\n";
print hexdump($exampleAText);
print "\n";
print hexdump($exampleATextUtf);
print "\n";

#############################
## SUB.
#############################
# For a given string parameter, returns a string which shows
# whether the utf8 flag is enabled and a byte-by-byte view
# of the internal representation.
#
sub hexdump
{
    my $str = shift;
    my $flag = Encode::is_utf8($str) ? 1 : 0;
    use bytes; # this tells unpack to deal with raw bytes
    my @internal_rep_bytes = unpack('C*', $str);
    return
        $flag
        . '('
        . join(' ', map { sprintf("%02x", $_) } @internal_rep_bytes)
        . ')';
}

Наконец, вывод:

exampleAText: {"key1":"a"} and as object: $VAR1 = {
          'key1' => 'a'
        };

exampleATextUtf: {"key1":"ä"} and as object: $VAR1 = {
          'key1' => "\x{e4}"
        };
 Key1 was: ä
0(7b 22 6b 65 79 31 22 3a 22 61 22 7d)
0(7b 22 6b 65 79 31 22 3a 22 c3 a4 22 7d)

Итак, мы видим, что в конце этого процесса ни одна из строк outpu не является строкой UTF-8, что ложно. По крайней мере, 0 (7b 22 6b 65 79 31 22 3a 22 c3 a4 22 7d). Обратите внимание, что c3 A4 является правильным байтовым кодом для ä http://www.utf8 -chartable.de /

Следовательно, библиотека, похоже, ожидает загрузки в строку non-utf-8 байтового кода utf-8, и в результате она будет делать то же самое, она выведет строку NON utf-8 с utf- 8-байтовый код.

Я не прав?

Дальнейшие эксперименты привели меня к выводам, что: Возвращаемые и потребляемые perlObjects имеют строки, помеченные как UTF-8 (как я и ожидал). Строки perl, используемые и возвращаемые при декодировании / кодировании, должны отображаться в perl как латинские строки ISO, но иметь байтовый код utf8. Поэтому при открытии файла, содержащего JSON в формате UTF8, не используйте «<: encoding (UTF-8)». </p>

0 голосов
/ 03 августа 2011

Суть проблемы заключалась в ожидании JSON массива октетов вместо символьной строки (решено в в этом вопросе ). Однако мне также не хватало нескольких вещей, связанных с юникодом, таких как «использование utf8». Вот разница, необходимая для полной работы кода в примере:

--- my_test.pl.orig 2011-08-03 15:44:44.217868886 +0200
+++ my_test.pl  2011-08-03 15:55:30.152379269 +0200
@@ -1,19 +1,14 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl -CSAD
 use strict;
 use warnings;
 use JSON;
 use File::Slurp;
 use Getopt::Long;
 use Encode;
-
-my $set_binmode = 0;
-GetOptions("set-binmode" => \$set_binmode);
-
-if ($set_binmode) {
-        binmode(STDIN,  ":encoding(UTF-8)");
-        binmode(STDOUT, ":encoding(UTF-8)");
-        binmode(STDERR, ":encoding(UTF-8)");
-}
+use utf8;
+use warnings qw< FATAL utf8 >;
+use open qw( :encoding(UTF-8) :std );
+use feature qw< unicode_strings >;

 sub check {
         my $text = shift;
@@ -21,8 +16,9 @@
 }

 my $my_test = "hei på deg";
-my $json_text = read_file('my_test.json');
-my $hash_ref = JSON->new->utf8->decode($json_text);
+my $json_text = read_file('my_test.json', binmode => ':encoding(UTF-8)');
+my $json_bytes = encode('UTF-8', $json_text);
+my $hash_ref = JSON->new->utf8->decode($json_bytes);

 print check($my_test), "\$my_test = $my_test\n";
 print check($json_text), "\$json_text = $json_text";
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...