Почему `join` и/или `JSON::to_json` автоматически преобразуют мои данные из целого числа в строку?

Я не понимаю, почему 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]

От сюда - 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

taliezin 14.12.2020 13:20

@taliezin Но почему Dumper не ведет себя так же? У него нет другой информации, кроме to_json.

A.H. 14.12.2020 13:26

Потому что Dumper и to_json не используют одну и ту же стратегию, чтобы определить, является ли скаляр строкой или числом: Dumperиспользует регулярное выражение , а to_json использует внутренние флаги скаляра.

Dada 14.12.2020 13:47

@taliezin: Спасибо. Хотели бы вы сформулировать это в ответ, который я могу принять?

A.H. 14.12.2020 13:56

Dumper использует регулярное выражение Data::Dumper по умолчанию делает это в коде XS. Поэтому я предполагаю, что он также анализирует переменную.

lordadmira 14.12.2020 22:21

@lordadmira Это правильно; Я ошибочно предположил, что версии XS и PP будут вести себя одинаково; они не делают. Код XS Data::Dumperпросто проверяет, включен ли флаг IOK , в то время как to_json проверяет, активен ли IOK, а POK нет в его версии PP, и сначала проверяет, активен ли POK, прежде чем проверять, активен ли IOK. в версии XS.

Dada 15.12.2020 10:28
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
6
198
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Когда вы преобразуете число в строку, преобразование в строку сохраняется в скаляре вместе с исходным числом. (Вы можете увидеть демонстрацию внизу моего ответа.)

Когда вы нумеруете строку, нумерация сохраняется в скаляре вместе с исходным номером.

Это оптимизация, поскольку скаляр часто преобразуется в строку или числовое значение более одного раза.

Это не проблема для 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

  1. Что ж, в Perl нет отдельных операторов для различных числовых типов, что может вызвать проблемы (например, -0 существует как число с плавающей запятой, но не как целое число), но такие проблемы возникают редко.

    Другая проблема заключается в том, что преобразование чисел с плавающей запятой часто приводит к потере информации.

Но Dumper и to_json имеют одни и те же данные, но решают дать разные результаты. Это баг или есть причины?

A.H. 14.12.2020 19:16

Их авторы приняли разные решения относительно того, какое из значений использовать. Это не ошибка, так как нет правильного ответа. например В случае стрингификации число было бы идеальным, но в случае нумификации цепочка была бы идеей. Но неизвестно, как был создан скаляр.

ikegami 14.12.2020 19:20

JSON::Types и Cpanel::JSON::XS::Type предоставляют подходы для более явного представления выходных данных JSON.

tobyink 15.12.2020 02:13

Это один из очень немногих случаев, когда вы должны поддерживать чистоту данных в 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, потому что это одна из областей, где требуется педантизм! Он очень старается правильно подобрать типы данных. В нем также есть обсуждение проблемы преобразования.

ХТН

Другие вопросы по теме