Perl生成的JSON中的UTF8字符串在客户端已损坏

时间:2019-02-15 08:10:07

标签: json perl unicode utf-8

我有一个Perl CGI脚本,该脚本正在从PostgreSQL数据库访问泰语和UTF-8字符串,并将它们作为JSON返回到基于Web的前端。当我从数据库中获取字符串以及将它们编码为JSON(基于写入日志文件)之后,这些字符串就很好了。但是,当客户端收到它们时,它们已损坏,例如:

功能名称“à¹\u0082รà¸\u0087à¹\u0080ร¸

很显然,一些字符正在转换为Unicode转义序列,但不是全部。

我真的可以就如何解决这个问题使用一些建议。

随后是简化的代码段。我正在使用'utf8'和'utf8 :: all'以及'JSON'。

在此先感谢您提供的帮助。

my $dataId = $cgi->param('dataid');
my $table = "uploadpoints";
my $sqlcommand = "select id,featurename from $table where dataid=$dataId;";
my $stmt = $gDbh->prepare($sqlcommand);
my $numrows = $stmt->execute;
# print JSON header
print <<EOM;
Content-type: application/json; charset="UTF-8"


EOM
my @retarray;
for (my $i = 0; ($i < $numrows); $i=$i+1)
{
    my $hashref = $stmt->fetchrow_hashref("NAME_lc");
    #my $featurename = $hashref->{'featurename'};
    #logentry("Point $i feature name is: $featurename\n");
    push @retarray,$hashref;
}
my $json = encode_json (\@retarray);
logentry("JSON\n $json");
print $json;

我修改并简化了示例,现在在本地运行,而不是通过浏览器调用运行:

my $dataId = 5; 
my $table = "uploadpoints";
my $sqlcommand = "select id,featurename from $table where dataid=$dataId and id=75;";
my $stmt = $gDbh->prepare($sqlcommand);
my $numrows = $stmt->execute;
my @retarray;
for (my $i = 0; ($i < $numrows); $i=$i+1)
{
    my $hashref = $stmt->fetchrow_hashref("NAME_lc");
    my $featurename = $hashref->{'featurename'};
    print "featurename $featurename\n";
    push @retarray,$hashref;
}
my $json = encode_json (\@retarray);
print $json;

使用Stefan例子中的hexdump,我确定从数据库读取的数据已经在UTF-8中。看起来好像它们已在JSON编码方法中重新编码。但为什么?

JSON中的数据使用的字节数恰好是原始UTF-8的两倍。

 perl testcase.pl | hexdump -C
00000000  66 65 61 74 75 72 65 6e  61 6d 65 20 e0 b9 82 e0  |featurename ....|
00000010  b8 a3 e0 b8 87 e0 b9 80  e0 b8 a3 e0 b8 b5 e0 b8  |................|
00000020  a2 e0 b8 99 e0 b9 81 e0  b8 88 e0 b9 88 e0 b8 a1  |................|
00000030  e0 b8 88 e0 b8 b1 e0 b8  99 e0 b8 97 e0 b8 a3 e0  |................|
00000040  b9 8c 0a 5b 7b 22 66 65  61 74 75 72 65 6e 61 6d  |...[{"featurenam|
00000050  65 22 3a 22 c3 a0 c2 b9  c2 82 c3 a0 c2 b8 c2 a3  |e":"............|
00000060  c3 a0 c2 b8 c2 87 c3 a0  c2 b9 c2 80 c3 a0 c2 b8  |................|
00000070  c2 a3 c3 a0 c2 b8 c2 b5  c3 a0 c2 b8 c2 a2 c3 a0  |................|
00000080  c2 b8 c2 99 c3 a0 c2 b9  c2 81 c3 a0 c2 b8 c2 88  |................|
00000090  c3 a0 c2 b9 c2 88 c3 a0  c2 b8 c2 a1 c3 a0 c2 b8  |................|
000000a0  c2 88 c3 a0 c2 b8 c2 b1  c3 a0 c2 b8 c2 99 c3 a0  |................|
000000b0  c2 b8 c2 97 c3 a0 c2 b8  c2 a3 c3 a0 c2 b9 c2 8c  |................|
000000c0  22 2c 22 69 64 22 3a 37  35 7d 5d                 |","id":75}]|
000000cb

还有其他建议吗?我尝试对UTF字符串使用解码,但是遇到了与宽字符有关的错误。

我确实阅读了汤姆·克里斯蒂安森(Tom Christianson)推荐的答案以及他的Unicode教程,但我承认其中很多内容是困扰我的。同样,我的问题似乎受到更大的限制。

我确实想知道是否获取哈希值并将其分配给普通变量是否在进行某种自动解码或编码。我不太了解Perl是何时使用其内部字符格式,而不是何时保留外部编码。

使用解决方案更新

结果表明,由于从数据库中检索到的字符串已经存在于UTF-8中,因此我需要使用“ to_json”而不是“ encode_json”。这解决了问题。尽管在该过程中了解了很多有关Perl Unicode处理的知识...

还建议:http://perldoc.perl.org/perluniintro.html

非常清晰的阐述。

1 个答案:

答案 0 :(得分:3)

注意::您可能还应该阅读此answer,相比之下,我的答案要低得多:-)

问题是您必须确保每个字符串采用哪种格式,否则您将获得错误的转换。处理UTF-8时,字符串可以采用两种格式:

  • 原始UTF-8编码的八位字节字符串,即public class Road { public string RoadID { get; set; } } [HttpPost] public async Task<ActionResult> DeleteRoad([System.Web.Http.FromBody]Road road) { Debug.WriteLine($"Road ID = { road.RoadID }"); return RedirectToAction("Index"); } 表示为两个八位字节Road ID = 4 Road ID = 4
  • 内部Perl字符串表示形式,即一个Unicode 字符 \x{100}U+0100 Ā LATIN CAPITAL LETTER A WITH MACRON

如果涉及I / O,则还需要知道I / O层是否进行UTF-8解码/编码。对于终端I / O,还必须考虑它是否理解UTF-8。两者结合在一起会使从代码中获取有意义的调试打印输出变得困难。

如果从源读取Perl代码后需要处理UTF-8字符串,则必须确保它们采用内部Perl格式。否则,当您调用需要Perl字符串而不是原始八位位组字符串的代码时,将会得到令人惊讶的结果。

我尝试在示例代码中显示这一点:

0xC4 0x80

从我的终端机(支持UTF-8)复制并粘贴。仔细观察两行之间的区别:

\x{100}

但是将其与以下内容进行比较,其中STDOUT不是终端,而是通过管道传输到另一个程序。十六进制转储始终显示“ c4 80”,即UTF-8编码。

#!/usr/bin/perl
use warnings;
use strict;

use JSON;

open(my $utf8_stdout, '>& :encoding(UTF-8)', \*STDOUT)
    or die "can't reopen STDOUT as utf-8 file handle: $!\n";

my $hex = "C480";
print "${hex}\n";

my $raw = pack('H*', $hex);
print STDOUT       "${raw}\n";
print $utf8_stdout "${raw}\n";

my $decoded;
utf8::decode($decoded = $raw);
print STDOUT       ord($decoded), "\n";
print STDOUT       "${decoded}\n"; # Wide character in print at...
print $utf8_stdout "${decoded}\n";

my $json = JSON->new->encode([$decoded]);
print STDOUT       "${json}\n"; # Wide character in print at...
print $utf8_stdout "${json}\n";

$json = JSON->new->utf8->encode([$decoded]);
print STDOUT       "${json}\n";
print $utf8_stdout "${json}\n";

exit 0;