为什么 `join` and/or `JSON::to_json` 将我的数据从整数静默转换为字符串?
Why do `join` and/or `JSON::to_json` convert my data silently from integer to string?
我不明白为什么 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);
输出 和 包含 join
:
的行
version: 1.2.3.4
[1,2,3,4]
["1","2","3","4"]
但是注释掉带有 join
的行 to_json
的输出突然显示整数而不是字符串,尽管 Data::Dumper
的输出仍然相同:
[1,2,3,4]
[1,2,3,4]
当您对数字进行字符串化时,字符串化与原始数字一起存储在标量中。 (你可以在我的回答底部看到一个演示。)
当您对字符串进行数值化时,数值化与原始编号一起存储在标量中。
这是一种优化,因为人们经常不止一次地对标量进行字符串化或数值化。
这对 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
但有时,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
这些不是您会遇到的唯一双重(或三重)变量。
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
这在 Perl 中不是问题,因为我们使用类型强制运算符(例如,==
适用于数字,eq
适用于字符串)。但是许多其他语言依赖于多态运算符(例如 ==
可用于比较字符串和比较数字)。[1]
但它确实给 JSON 序列化程序带来了问题,这些序列化程序被迫将单一类型分配给标量。如果 $x
既包含字符串又包含数字,应该使用哪一个?
如果标量是字符串化的结果,使用数字是理想的,但如果标量是数值化的结果,则字符串是理想的。无法判断这些来源中的哪一个属于标量(如果有的话),因此该模块的作者面临艰难的选择。
理想情况下,他们会提供不同的界面,但这可能会增加复杂性和性能损失。
您可以使用 Devel::Peek 的 Dump
查看标量的内部结构。相关行是 FLAGS
行。
IOK
无 IsUV
:包含有符号整数
IOK
和 IsUV
:包含一个无符号整数
NOK
:包含一个浮点数
POK
无 UTF8
:包含降级字符串
POK
和 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
嗯,Perl 没有针对不同数字类型的单独运算符,这可能会导致问题(例如 -0 存在一个 float,但不是一个 int),但这些问题很少遇到.
另一个问题是浮点数的字符串化常常导致信息丢失。
这是您必须在 Perl 中保持数据纯度的极少数情况之一。一旦创建了某种类型的变量,就绝不能在任何其他类型的上下文中使用它。如果确实需要,请先将其复制到新变量以保留原始变量。
use feature 'say';
use Data::Dumper;
use JSON;
my @version = (1, 2, 3, 4);
{ say "version: ", join ".", my @copy = @version; }
$Data::Dumper::Terse = 1;
$Data::Dumper::Indent = 0;
say Dumper(\@version);
say to_json(\@version);
打印:
version: 1.2.3.4
[1,2,3,4]
[1,2,3,4]
我还建议使用 Cpanel::JSON::XS,因为这是需要迂腐的地方!它非常努力地使数据类型正确。它还对转换问题进行了一些讨论。
HTH
我不明白为什么 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);
输出 和 包含 join
:
version: 1.2.3.4
[1,2,3,4]
["1","2","3","4"]
但是注释掉带有 join
的行 to_json
的输出突然显示整数而不是字符串,尽管 Data::Dumper
的输出仍然相同:
[1,2,3,4]
[1,2,3,4]
当您对数字进行字符串化时,字符串化与原始数字一起存储在标量中。 (你可以在我的回答底部看到一个演示。)
当您对字符串进行数值化时,数值化与原始编号一起存储在标量中。
这是一种优化,因为人们经常不止一次地对标量进行字符串化或数值化。
这对 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
但有时,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
这些不是您会遇到的唯一双重(或三重)变量。
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
这在 Perl 中不是问题,因为我们使用类型强制运算符(例如,==
适用于数字,eq
适用于字符串)。但是许多其他语言依赖于多态运算符(例如 ==
可用于比较字符串和比较数字)。[1]
但它确实给 JSON 序列化程序带来了问题,这些序列化程序被迫将单一类型分配给标量。如果 $x
既包含字符串又包含数字,应该使用哪一个?
如果标量是字符串化的结果,使用数字是理想的,但如果标量是数值化的结果,则字符串是理想的。无法判断这些来源中的哪一个属于标量(如果有的话),因此该模块的作者面临艰难的选择。
理想情况下,他们会提供不同的界面,但这可能会增加复杂性和性能损失。
您可以使用 Devel::Peek 的 Dump
查看标量的内部结构。相关行是 FLAGS
行。
IOK
无IsUV
:包含有符号整数IOK
和IsUV
:包含一个无符号整数NOK
:包含一个浮点数POK
无UTF8
:包含降级字符串POK
和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
嗯,Perl 没有针对不同数字类型的单独运算符,这可能会导致问题(例如 -0 存在一个 float,但不是一个 int),但这些问题很少遇到.
另一个问题是浮点数的字符串化常常导致信息丢失。
这是您必须在 Perl 中保持数据纯度的极少数情况之一。一旦创建了某种类型的变量,就绝不能在任何其他类型的上下文中使用它。如果确实需要,请先将其复制到新变量以保留原始变量。
use feature 'say';
use Data::Dumper;
use JSON;
my @version = (1, 2, 3, 4);
{ say "version: ", join ".", my @copy = @version; }
$Data::Dumper::Terse = 1;
$Data::Dumper::Indent = 0;
say Dumper(\@version);
say to_json(\@version);
打印:
version: 1.2.3.4
[1,2,3,4]
[1,2,3,4]
我还建议使用 Cpanel::JSON::XS,因为这是需要迂腐的地方!它非常努力地使数据类型正确。它还对转换问题进行了一些讨论。
HTH