Я не понимаю, почему 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]
@taliezin Но почему Dumper
не ведет себя так же? У него нет другой информации, кроме to_json
.
Потому что Dumper
и to_json
не используют одну и ту же стратегию, чтобы определить, является ли скаляр строкой или числом: Dumper
использует регулярное выражение , а to_json
использует внутренние флаги скаляра.
@taliezin: Спасибо. Хотели бы вы сформулировать это в ответ, который я могу принять?
Dumper использует регулярное выражение Data::Dumper
по умолчанию делает это в коде XS. Поэтому я предполагаю, что он также анализирует переменную.
@lordadmira Это правильно; Я ошибочно предположил, что версии XS и PP будут вести себя одинаково; они не делают. Код XS Data::Dumper
просто проверяет, включен ли флаг IOK , в то время как to_json
проверяет, активен ли IOK, а POK нет в его версии PP, и сначала проверяет, активен ли POK, прежде чем проверять, активен ли IOK. в версии XS.
Когда вы преобразуете число в строку, преобразование в строку сохраняется в скаляре вместе с исходным числом. (Вы можете увидеть демонстрацию внизу моего ответа.)
Когда вы нумеруете строку, нумерация сохраняется в скаляре вместе с исходным номером.
Это оптимизация, поскольку скаляр часто преобразуется в строку или числовое значение более одного раза.
Это не проблема для 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's 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 существует как число с плавающей запятой, но не как целое число), но такие проблемы возникают редко.
Другая проблема заключается в том, что преобразование чисел с плавающей запятой часто приводит к потере информации.
Но Dumper
и to_json
имеют одни и те же данные, но решают дать разные результаты. Это баг или есть причины?
Их авторы приняли разные решения относительно того, какое из значений использовать. Это не ошибка, так как нет правильного ответа. например В случае стрингификации число было бы идеальным, но в случае нумификации цепочка была бы идеей. Но неизвестно, как был создан скаляр.
JSON::Types и Cpanel::JSON::XS::Type предоставляют подходы для более явного представления выходных данных JSON.
Это один из очень немногих случаев, когда вы должны поддерживать чистоту данных в 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, потому что это одна из областей, где требуется педантизм! Он очень старается правильно подобрать типы данных. В нем также есть обсуждение проблемы преобразования.
ХТН
От сюда -
Simple Perl scalars (any scalar that is not a reference) are the most difficult objects to encode: this module will encode undefined scalars as JSON null values, scalars that have last been used in a string context before encoding as JSON strings, and anything else as number value