Perl的JSON::XS不能正确编码UTF8吗?

10
这段简单的代码展示了我在Perl中使用JSON::XS编码时遇到的问题:
#!/usr/bin/perl
use strict;
use warnings;
use JSON::XS; 
use utf8;
binmode STDOUT, ":encoding(utf8)";

my (%data);

$data{code} = "Gewürztraminer";
print "data{code} = " . $data{code} . "\n";

my $json_text = encode_json \%data;
print $json_text . "\n";

这将产生以下输出:
johnnyb@boogie:~/Projects/repos > ./jsontest.pl 
data{code} = Gewürztraminer
{"code":"Gewürztraminer"}

现在,如果我注释掉上面的binmode行,我会得到:
johnnyb@boogie:~/Projects/repos > ./jsontest.pl 
data{code} = Gew�rztraminer
{"code":"Gewürztraminer"}

这里发生了什么情况?请注意,我正在尝试在 perl CGI 脚本中修复这种行为,其中无法使用 binmode,但是我始终会在 JSON 流中返回如上所示的 "ü" 字符。我应该如何调试?我错过了什么吗?

将最后一行替换为 print decode('UTF-8', $json_text, Encode::FB_CROAK) . "\n"; - Håkon Hægland
2个回答

14

encode_jsonJSON::XS->new->utf8->encode的简写形式,它使用UTF-8进行编码,然后您通过将其打印到已添加编码层的STDOUT上来重新编码它。实际上,您正在执行encode_utf8(encode_utf8($uncoded_json))

解决方案1

use open ':std', ':encoding(utf8)';  # Defaults
binmode STDOUT;                      # Override defaults
print encode_json(\%data);

解决方案 2

use open ':std', ':encoding(utf8)';    # Defaults
print JSON::XS->new->encode(\%data);   # Or to_json from JSON.pm

解决方案3

以下方法适用于标准输出的任何编码,通过使用\u转义字符来处理非ASCII字符:

print JSON::XS->new->ascii->encode(\%data);
在评论中,你提到它实际上是一个CGI脚本。
#!/usr/bin/perl
use strict;
use warnings;

use utf8;                      # Encoding of source code.
use open ':encoding(UTF-8)';   # Default encoding of file handles.
BEGIN {
   binmode STDIN;                       # Usually does nothing on non-Windows.
   binmode STDOUT;                      # Usually does nothing on non-Windows.
   binmode STDERR, ':encoding(UTF-8)';  # For text sent to the log file.
}

use CGI      qw( -utf8 );
use JSON::XS qw( ); 

{
   my $cgi = CGI->new();
   my $data = { code => "Gewürztraminer" };
   print $cgi->header('application/json');
   print encode_json($data);
}

是的 - 这确实有效,并且如下所述,我认为我正在编码已经是UTF8的数据。不确定如何解决这个问题。不幸的是,STDOUT在CGI脚本中没有意义(?),所以我不确定我是否可以使用上面的方法。 - Omortis
1
你说的话毫无意义。如果你不使用STDOUT,你就不会遇到这个问题。而且,CGI确实使用STDOUT。 - ikegami
我一直没有弄清楚如何将CGI对象用作文件句柄并传递给binmode。编码的JSON数据必须打印到CGI对象中,以便返回给发出AJAX请求的jQuery。如果您知道如何做到这一点,我全神贯注地倾听和观察。 - Omortis
你在说什么呢?我们已经确认了CGI使用STDOUT,所以你需要使用我发布的完全相同的代码。 - ikegami
2
@Omortis CGI.pm 不会将任何东西发送到任何地方。你的代码需要 print 一些东西。CGI.pm 只是为你生成 HTML 的老旧功能。还要记住,CGI.pm 不再是核心模块了。 :) - simbabque
显示剩余2条评论

3
JSON::XS将其输出编码为八位字节。这意味着编码的utf8字符串的外部表示形式,但它不是unicode字符串。有关详细信息,请参见perlunicode。简而言之,$json_text的内容是通过二进制代码在IO处理程序中传输的。如果你在use utf8;之后创建$data{code}的标量内容,则会得到包含内部编码的unicode字符字符串的标量(内部编码为utf8,但这是你不应该依赖的实现细节。Pragma use utf8;表示源代码编码为utf8,没有别的)。如果你想将两个标量以utf8编码的IO处理程序输出,则必须将$json_string转换为内部unicode字符字符串。
use strict;
use warnings;
use JSON::XS; 
use utf8;
binmode STDOUT, ":encoding(utf8)";

my (%data);

$data{code} = "Gewürztraminer";
print "data{code} = " . $data{code} . "\n";

my $json_text = encode_json \%data;
utf8::decode($json_text);
print $json_text . "\n";

或者说它是如何使用的,使用IO处理程序以二进制模式输出编码字符串。

my $json_text = encode_json \%data;
binmode STDOUT;
print $json_text . "\n";

尝试

print utf8::is_utf8($json_text) ? "UTF8" : "OCTETS" . "\n";

查看内部内容。


1
是的,但重点在于解释出了什么问题,而不是让它变得更有效率。 - Hynek -Pichi- Vychodil
1
代码并没有解释,而是解释说明了。 - ikegami
“use utf8” 这个编译指示只是为了让我能够在这里粘贴的源代码中使用 “ü”,并不包含在原始的 CGI Perl 代码中。我同意它会让例子变得有些混乱。在 CGI 源代码中(很难有意义地分享),UTF8 数据来自 DBI 数据库查询,并且在通过 encode_json 传递之前是正确的。数据在哈希 %data 中是正确的(由 CGI 工具显示),但一旦编码就不正确,表现出与上述行为完全相同的行为(第一次运行)。问题是我正在尝试编码已经是 UTF8 的数据吗? - Omortis
1
永远不要使用 is_utf8。如果您需要强制使用其中一种存储格式,请使用 utf8::upgradeutf8::downgrade,但在此情况下都不需要使用它们。 - ikegami
1
@Omortis:你从DBI读取数据了吗?我们来看看。检查一下从DBI读取的数据是否在内部标记为Unicode,使用is_utf8,如果没有,则使用utf8::upgrade。这是DBI驱动程序中常见的错误。然后按照@ikegami的答案选择您想要在二进制或utf8模式下进行IO。 - Hynek -Pichi- Vychodil
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接