为什么JSON::XS不能生成有效的UTF-8?

为什么JSON::XS不能生成有效的UTF-8?,json,perl,utf-8,Json,Perl,Utf 8,我得到了一些损坏的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->$_, ':en

我得到了一些损坏的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
utf8.t。。
ok 1-字符串:交付«法国面包»
不正常2-JSON:{“值”:“交付«法国面包»”}
#测试“JSON:{“value”:“Deliver«French Bread»”}失败
#在utf8.t第17行。
#在第18个字符处找到了狡猾的字符“”
#字符串未标记为utf8…它是要标记的吗?
#可能最初是一个指向左侧的双角度引号char-codepoint 171(dec),ab(hex)
{“价值”:“交付«法国面包»”}
ok 3-字符串:日本国
ok 4-JSON:{“值”:“æ¥æå½”}
1..4
{“值”:日本国"}
# 187
因此,包含guillemets(«»)的字符串是有效的UTF-8,但生成的JSON不是。我缺少什么呢?
utf8
pragma正确地标记了我的源代码。此外,后面的187来自diag。这小于255,因此看起来几乎像是Perl中旧Unicode错误的变体。(而且测试输出看起来仍然像垃圾。使用test::Builder永远都不可能做到这一点)

切换到
JSON::PP
会产生相同的输出


这是在OS X Yosemite上运行的Perl 5.18.1。

是合理的,utf8
不做您认为它做的事情。您应该将解码后的字符串传递给它。我不确定它的意义是什么,但它不是正确的工具。如果您想检查字符串是否有效,可以使用UTF-8

ok(eval { decode_utf8($string, Encode::FB_CROAK | Encode::LEAVE_SRC); 1 },
   '$string is valid UTF-8');

为了证明JSON::XS是正确的,让我们看看序列
是否被标记为正常的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("");
}
输出(添加了一些空格):


JSON::XS正在生成有效的UTF-8,但是您正在两个不同的上下文中使用生成的UTF-8编码的字节字符串,而这两个上下文需要字符串

问题1:测试::utf8 以下是
正常时utf8
将失败的两种主要情况:

  • 您有一个错误编码的字符串,它是从UTF-8字节字符串解码的,就像它是拉丁语-1或双编码UTF-8,字符串非常好,看起来像一个潜在的“危险”错误编码(使用文档中的术语)
  • 您有一个有效的UTF-8字节字符串,其中包含编码的代码点U+0080到U+00FF,例如
    «法国面包»
  • 是正常的\u utf8
    测试仅用于字符串,并且有可能出现误报

    问题2:输出编码 所有非JSON字符串都是字符串,而JSON字符串是从JSON编码器返回的UTF-8编码字节字符串PerlIO层对于TAP输出,字符串被隐式编码为UTF-8,结果良好,而包含JSON的字节字符串被双重编码。但是,STDERR没有设置
    :encoding
    PerlIO层,因此编码的JSON字节字符串在
    警告中看起来很好,因为它们已经存在编码并被直接传递出去


    仅使用
    :encoding(UTF-8)
    PerlIO层来处理带有字符串的IO,而不是默认情况下从JSON编码器返回的UTF-8编码字节字符串。

    进一步的测试表明,JSON::XS似乎会在127到255的所有字符上创建损坏的输出。
    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