我得到了一些损坏的JSON,我已将其简化为这个测试用例。
use utf8;
use 5.18.0;
use Test::More;
use Test::utf8;
use JSON::XS;
BEGIN {
# damn it
my $builder = Test::Builder->new;
foreach (qw/output failure_output todo_output/) {
binmode $builder->$_, ':encoding(UTF-8)';
}
}
foreach my $string ( 'Deliver «French Bread»', '日本国' ) {
my $hashref = { value => $string };
is_sane_utf8 $string, "String: $string";
my $json = encode_json($hashref);
is_sane_utf8 $json, "JSON: $json";
say STDERR $json;
}
diag ord('»');
done_testing;
这是输出:
utf8.t ..
ok 1 - String: Deliver «French Bread»
not ok 2 - JSON: {"value":"Deliver «French Bread»"}
# Failed test 'JSON: {"value":"Deliver «French Bread»"}'
# at utf8.t line 17.
# Found dodgy chars "<c2><ab>" at char 18
# String not flagged as utf8...was it meant to be?
# Probably originally a LEFT-POINTING DOUBLE ANGLE QUOTATION MARK char - codepoint 171 (dec), ab (hex)
{"value":"Deliver «French Bread»"}
ok 3 - String: 日本国
ok 4 - JSON: {"value":"æ¥æ¬å½"}
1..4
{"value":"日本国"}
# 187
因此,包含 guillemets («»( 的字符串是有效的 UTF-8,但生成的 JSON 不是。我错过了什么?utf8
编译指示正确标记了我的源。此外,尾随 187 来自诊断。这不到 255,所以它看起来几乎像是 Perl 中旧 Unicode 错误的变体。(测试输出看起来仍然像废话。永远无法通过测试::构建器做到这一点(。
切换到JSON::PP
会产生相同的输出。
这是在OS X Yosemite上运行的Perl 5.18.1。
is_sane_utf8
不会做你认为它做的事情。你应该将你解码的字符串传递给它。我不确定它的意义何在,但它不是正确的工具。如果要检查字符串是否有效 UTF-8,可以使用
ok(eval { decode_utf8($string, Encode::FB_CROAK | Encode::LEAVE_SRC); 1 },
'$string is valid UTF-8');
为了证明 JSON::XS 是正确的,让我们看一下标记is_sane_utf8
序列。
+--------------------- Start of two byte sequence
| +---------------- Not zero (good)
| | +---------- Continuation byte indicator (good)
| | |
v v v
C2 AB = [110]00010 [10]101011
00010 101011 = 000 1010 1011 = U+00AB = «
下面显示 JSON::XS 生成与 Encode.pm 相同的输出:
use utf8;
use 5.18.0;
use JSON::XS;
use Encode;
foreach my $string ('Deliver «French Bread»', '日本国') {
my $hashref = { value => $string };
say(sprintf("Input: U+%v04X", $string));
say(sprintf("UTF-8 of input: %v02X", encode_utf8($string)));
my $json = encode_json($hashref);
say(sprintf("JSON: %v02X", $json));
say("");
}
输出(添加一些空格(:
Input: U+0044.0065.006C.0069.0076.0065.0072.0020.00AB.0046.0072.0065.006E.0063.0068.0020.0042.0072.0065.0061.0064.00BB
UTF-8 of input: 44.65.6C.69.76.65.72.20.C2.AB.46.72.65.6E.63.68.20.42.72.65.61.64.C2.BB
JSON: 7B.22.76.61.6C.75.65.22.3A.22.44.65.6C.69.76.65.72.20.C2.AB.46.72.65.6E.63.68.20.42.72.65.61.64.C2.BB.22.7D
Input: U+65E5.672C.56FD
UTF-8 of input: E6.97.A5.E6.9C.AC.E5.9B.BD
JSON: 7B.22.76.61.6C.75.65.22.3A.22.E6.97.A5.E6.9C.AC.E5.9B.BD.22.7D
JSON::XS 正在生成有效的 UTF-8,但您在两个需要字符串的不同上下文中使用生成的 UTF-8 编码字节字符串。
问题 1:测试::utf8
以下是is_sane_utf8
失败的两种主要情况:
- 您有一个错误编码的字符串,该字符串是从
- UTF-8 字节字符串(就好像它是拉丁语-1(或双编码的 UTF-8 解码的,或者该字符串完全没问题,看起来像一个潜在的"狡猾"错误编码(使用其文档中的术语(。
- 您有一个有效的 UTF-8 字节字符串,其中包含编码的代码点 U+0080 到 U+00FF,例如
«French Bread»
。
is_sane_utf8
测试仅适用于字符串,并且记录了漏报的可能性。
问题 2:输出编码
所有非 JSON 字符串都是字符串,而 JSON 字符串是从 JSON 编码器返回的 UTF-8 编码字节字符串。由于您使用 :encoding(UTF-8)
PerlIO 层进行 TAP 输出,因此字符串被隐式编码为 UTF-8 并具有良好的结果,而包含 JSON 的字节字符串则被双重编码。但是,STDERR 没有 :encoding
PerlIO 层集,因此编码的 JSON 字节字符串在您的warn
中看起来不错,因为它们已经编码并直接传递出去。
仅将 :encoding(UTF-8)
PerlIO 层用于带有字符串的 IO,而不是默认情况下从 JSON 编码器返回的 UTF-8 编码字节字符串。