为什么`join` 和/或`JSON::to_json` 默默地将我的数据从整数转换为字符串?

A.H*_*.H. 6 perl

我不明白为什么join更改JSON::to_string以下示例中的输出:

#!/usr/bin/perl
use v5.26;
use Data::Dumper;
use JSON;

my @version = (1, 2, 3, 4);

say "version: ", join ".", @version;    # comment this line out

$Data::Dumper::Terse = 1;
$Data::Dumper::Indent = 0;

say Dumper(\@version);
say to_json(\@version);
Run Code Online (Sandbox Code Playgroud)

输出包含该行join

version: 1.2.3.4
[1,2,3,4]
["1","2","3","4"]
Run Code Online (Sandbox Code Playgroud)

但是用join的输出注释掉该行会to_json突然显示整数而不是字符串,尽管 的输出Data::Dumper仍然相同:

[1,2,3,4]
[1,2,3,4]
Run Code Online (Sandbox Code Playgroud)

ike*_*ami 6

当您对数字进行字符串化时,字符串化将与原始编号一起存储在标量中。(你可以在我的答案底部看到一个演示。)

当您对字符串进行数字化时,数字化与原始编号一起存储在标量中。

这是一种优化,因为人们经常不止一次地对标量进行字符串化或数字化。

这对 Perl 来说不是问题,因为 Perl 有强制操作符而不是多态操作符。但它让 JSON 序列化器的作者处于困难的境地,要么需要额外的信息,要么猜测应该使用标量包含的哪些值。

您可以使用$x = 0 + $x;.

您可以使用$x = "$x";.

更详细的回答如下。


Perl 可以自由地将内部格式更改为它认为合适的标量。这通常作为修改标量的一部分来完成。

$x = 123;          # $x contains a signed integer
$x += 0.1;         # $x contains a float

$x = 2147483647;   # $x contains a signed integer
++$x;              # $x contains an unsigned integer (on a build with 32-bit ints)

$x = "123";        # $x contains a downgraded string
$x += 0;           # $x contains a signed integer

$x = "abc";        # $x contains a downgraded string
$x .= "\x{2660}";  # $x contains an upgraded string
Run Code Online (Sandbox Code Playgroud)

但有时,Perl 会向标量添加第二个值作为优化。

$x = 123;          # $x contains a signed integer
$x * 0.1;          # $x contains a signed integer AND a float

$x = 123;          # $x contains a signed integer
"$x";              # $x contains a signed integer AND a downgraded string

$x = "123";        # $x contains a downgraded string
$x+0;              # $x contains a signed integer AND a downgraded string
Run Code Online (Sandbox Code Playgroud)

这些不是您会遇到的唯一双重(或三重)变量。

my $x = !!0;        # $x contains a signed integer AND a float AND a downgraded string
"$!";               # $! contains a float (not a signed integer?!) AND a downgraded string
Run Code Online (Sandbox Code Playgroud)

这在 Perl 中不是问题,因为我们使用类型强制操作符(例如==适用于数字,eq适用于字符串)。但是许多其他语言依赖于多态运算符(例如,==可用于比较字符串和比较数字)。[1]

但它确实给 JSON 序列化器带来了一个问题,它们被迫将单一类型分配给标量。如果同时$x包含一个字符串和一个数字,应该使用哪个?

如果标量是字符串化的结果,使用数字将是理想的,但如果标量是数字化的结果,则字符串将是理想的。无法确定这些起源中的哪一个与标量(如果有)有关,因此模块的作者面临着艰难的选择。

理想情况下,他们会提供不同的接口,但这可能会增加复杂性和性能损失。


您可以使用 Devel::Peek's 查看标量的内部结构Dump。相关的线是FLAGS线。

  • IOKwithout IsUV: 包含一个有符号整数
  • IOKwith IsUV: 包含一个无符号整数
  • NOK: 包含一个浮点数
  • POKwithout UTF8:包含降级的字符串
  • POKwith UTF8: 包含升级后的字符串
  • ROK: 包含参考
$ perl -MDevel::Peek -e'$x=123; Dump($x); "$x"; Dump($x);' 2>&1 |
   perl -M5.014 -ne'next if !/FLAGS/; say join ",", /\b([INPR]OK|IsUV|UTF8)/g'
IOK
IOK,POK

$ perl -MDevel::Peek -e'$x="123"; Dump($x); 0+$x; Dump($x);' 2>&1 |
   perl -M5.014 -ne'next if !/FLAGS/; say join ",", /\b([INPR]OK|IsUV|UTF8)/g'
POK
IOK,POK
Run Code Online (Sandbox Code Playgroud)
  1. 好吧,Perl 没有针对不同数字类型的单独运算符,这可能会导致问题(例如 -0 存在浮点数,但不是整数),但这些问题很少遇到。

    另一个问题是浮点数的字符串化通常会导致信息丢失。