【问题标题】:Problem with decoding UTF-8 JSON in perl在 perl 中解码 UTF-8 JSON 的问题
【发布时间】:2011-09-26 08:38:18
【问题描述】:

使用 JSON 库处理时,UTF-8 字符会被破坏(也许这类似于 Problem with decoding unicode JSON in 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所指出的那样

我可以在我的 Perl 源代码中使用 Unicode 吗?

是的,你可以!如果您的来源是 UTF-8 编码,可以表示 使用 utf8 pragma。

use utf8;

这不会对您的 输入或输出。它只是 影响您的来源的方式 读。您可以在字符串中使用 Unicode 字面量,在标识符中(但它们 还是要“字字” 根据 \w ),甚至在自定义 分隔符。

【问题讨论】:

    标签: perl json utf-8


    【解决方案1】:

    您将程序保存为 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
    

    【讨论】:

    • 感谢您的回答,但它没有回答我的问题。在程序末尾添加 print $$hash_ref{'my_test'}, "\n", print $my_test, "\n"; 会以 iso-8859-1 格式打印两次。正如 perlunifaq 所指出的,use utf8 只影响源代码,而不影响 I/O。
    • 把Perl字符串打印到STDOUT是错误的;您必须首先将它们编码为八位字节,明确地use Encode qw(encode); print encode 'UTF-8', $my_test; 或隐含地binmode STDOUT, ':encoding(UTF-8)'; print $my_test;。此外,这不是 ISO-8859-1,而是 Perl 的内部编码,由于历史原因,它非常复杂,只是 碰巧 看起来像你所说的那样。你需要对编码这个话题进行严格的介绍,去阅读p3rl.org/UNI
    • daxim 的这个答案在简单性方面相当完美(我认为)。谢谢!
    【解决方案2】:

    问题的核心是 JSON 对八位字节数组而不是字符串的期望(在 this question 中解决)。但是我也错过了一些与 unicode 相关的东西,比如“使用 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";
    

    【讨论】:

      【解决方案3】:

      这只是我的印象,还是这个 perl 库希望你将 UTF-8 字节码写入 isoLatin1 字符串(字符串上禁用 utf-8 标志); 同样,它会以 iso 拉丁字符串形式返回给您的 UTF-8 字节码:

      #! /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)
      

      因此,我们看到在此过程结束时,输出字符串都不是 UTF-8 字符串,这是错误的。至少,0(7b 22 6b 65 79 31 22 3a 22 c3 a4 22 7d)。 请注意,c3 A4 是 ä 的正确字节码 http://www.utf8-chartable.de/

      因此,库似乎希望将 utf-8 字节码写入一个非 utf-8 字符串,结果,它会做同样的事情,它会输出一个带有 utf- 的非 utf-8 字符串8字节码。

      我错了吗?

      进一步的实验使我得出以下结论: 返回和使用的 perlObjects 具有标记为 UTF-8 的字符串(如我所料)。 从 decode/encode 消费和返回的 perl 字符串在 perl 中必须显示为 ISO latin 1 字符串,但具有 utf8 字节码。 因此,当打开一个包含 UTF8 json 的文件时,不要使用“<:encoding>

      【讨论】:

        猜你喜欢
        • 2018-02-07
        • 1970-01-01
        • 1970-01-01
        • 2018-06-06
        • 2018-06-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-12
        相关资源
        最近更新 更多