Нельзя просто так взять и распарсить этот JSON на JavaScript +57


JSON является одним из очень простых, но в то же время эффективных языков для хранения и передачи данных. Он настолько популярен, что, пожалуй, может считаться самым совместимым форматом представления данных в мире. Вероятно, что все современные языки программирования умеют работать с JSON практически из коробки.

Одновременно с этим, JavaScript является одним из наиболее популярных языков программирования и применяется практически везде, начиная от веб-браузеров, заканчивая серверами и инструментами разработки. Также, нужно понимать, что JSON появился напрямую из JavaScript и эти два языка просто созданы друг для друга.

Но что же может пойти не так, спросите Вы? Просто попробуйте распарсить следующий JSON-документ:

{ "foo": 123456789123456789123 }

Давайте попробуем самый простой способ:

const object = JSON.parse('{ "foo": 123456789123456789123 }');

console.log(object.foo);

// Outputs: 123456789123456800000

Что же здесь произошло? Очевидно результат выполнения: 123456789123456800000 отличается от оригинального значения из JSON-документа: 123456789123456789123

Дело в том, что указанное число слишком велико, чтобы быть представленным стандартными типами данных, которые можно найти в современных языках программирования. Обычно, самым большим типом данных, который часто поддерживается языками программирования, является unsigned int 64, т. е. целое 64-битное число без знака (что соответствует размерам регистра современного 64-битного процессора), тогда как наше число требует 67 битов для корректного представления. Это и приводит к потере точности при попытке записать число в память компьютера. И стоит понимать, что это происходит не только с JavaScript, но и со всеми языками программирования, которые имеют ограничение на размер хранимого числа.

Чтобы избежать этой проблемы, как правило, современные языки программирования имеют специальный тип данных, обычно называемый BigInt или каким-то похожим образом. Наличие такого типа очень важно для научных вычислений.

Приведем пример, как наш JSON-документ можно распарсить, например, на Go с использованием специального типа:

package main

import (
 "encoding/json"
 "fmt"
 "math/big"
)

func main() {

 var object struct {
  Foo big.Int `json:"foo"`
 }
 
 b := []byte(`{ "foo": 123456789123456789123 }`)

 if err := json.Unmarshal(b, &object); err != nil {
  panic(err)
 }
 
 fmt.Println(object.Foo.String())
 
 // Outputs: 123456789123456789123
 
}

Благодаря использованию типа big.Int вышеприведенный код отлично распарсит наше число без потери точности. Как правило, любой другой язык программирования также позволяет добиться аналогичного результата похожим образом.

Попробуем повторить это на JavaScript?

Обратим внимание на параметры JSON.parse. Вторым аргументом метода является специальная callback-функция называемая "reviver" (восстановитель). По-сути это функция десериализации, которая вызывается для каждого значения (value) из распарсиваемого JSON-документа и позволяет менять значение на усмотрение пользователя. Также, в JavaScript есть специальный тип BigInt, позволяющий работать с большими целыми числами.

Супер! Давайте теперь объединим это всё вместе:

const object = JSON.parse(
  '{ "foo": 123456789123456789123 }',
  (key, value) => (key === 'foo') ? BigInt(value) : value
);

console.log(object.foo);

// Outputs: 123456789123456794624n

В реализации выше мы проверяем, что ключ соответствует значению: "foo", и конвертируем значение из JSON-документа в BigInt.

Но подождите! Полученный результат: 123456789123456794624n снова отличается от ожидаемого нами: 123456789123456789123. В чем дело на этот раз?

Немного копнув глубже, оказывается, что в далеком 2009-ом году разработчики стандарта EcmaScript (JavaScript) проделали не самую хорошую работу. Дело в том, что значение (value), которое передается в пользовательскую функцию (reviver) уже является предварительно десериализованным. Фактически, это приводит к выполнению примерно следующего кода:

(key === 'foo') ? BigInt(123456789123456800000) : 123456789123456800000

Другими словами, как бы Вы того не хотели, не существует способа корректно распарсить это число используя нативный JSON-парсер на JavaScript.

Конечно, в защиту создателей EcmaScript можно сказать, что в то время типа BigInt не существовало и в помине, но, тем не менее, сейчас нам от этого не легче.

Hidden text

Извиняюсь, что перебил!

Недавно я запустил свой Telegram-канал по разработке на JavaScript, там я планирую делиться опытом, новостями и наработанными практиками из мира JS. Заходите не стесняйтесь :)

Но есть и хорошие новости!

Комитет TC39 (группа экспертов отвечающих за разработку современного стандарта EcmaScript) рассматривает предложение об улучшении языка (JSON.parse source text access proposal), которое добавляет возможность доступа к исходному тексту JSON-значения в reviver-функцию. Данный стандарт находится уже на третьей стадии (Stage 3) и даже реализован в движке V8 (на котором работает Chromium и Node.js). Достаточно включить флаг: harmony_json_parse_with_source.

Реализация этого функционала в V8 появилась относительно недавно: 10-го Декабря 2022 года, однако само предложение толком не двигается вперед уже более двух лет и, кажется, что мейнтейнеры не особо торопятся его финализировать. Конечно, такая вещь как изменение стандарта требует времени. Думаю пройдет еще не один год, прежде чем данный функционал будет полностью реализован и внедрен во все JS-движки.

Почему это важно?

Нынче я работаю с технологиями Web 3.0 и в этой сфере очень часто можно встретить не просто большие, но огромные числа. Возьмем к примеру Ethereum. Один эфир (Ether) в этой системе равен 1,000,000,000,000,000,000 WEI (1E18), согласитесь квинтиллион — это довольно большое число. В системах, которые работают с финансами принято хранить денежные значения как целое число условных "центов" (WEI в данном примере), а затем просто округлять такие значения при отображении пользователю. Это позволяет добиться максимальной точности при всех вычислениях.

В то же время формат JSON используется, наверное, в 99% различных API и нам нужен способ передавать такие большие числа между системами. Многие API представляют большие числа не как числа, а как строки, содержащие числа. Отчасти это решает проблему на стороне JS, но это достаточно коряво и нарушает семантику стандарта JSON. Должен быть способ лучше!

Способ лучше

Приняв все вышеизложенное, я решил разработать собственный JSON-парсер на JavaScript, который бы учел все имеющиеся проблемы с парсингом JSON на JS в одном готовом решении и мне удалось оформить это как полноценный полифил. Теперь не нужно ждать внедрения стандарта несколько лет перед его использованием. Возможно это даже поможет с внедрением самого стандарта.

Вот пример как можно использовать новый стандарт совместно с полифилом для нашего случая:

import '@ton.js/json-parse-polyfill';

const object = JSON.parse(
  '{ "foo": 123456789123456789123 }',
  (key, value, context) => (
    (key === 'foo' ? BigInt(context.source) : value)
  )
);

console.log(object.foo);

// Outputs: 123456789123456789123n

Как видите достаточно только импортировать полифил и немного поменять reviver-функцию. Наконец-то оно работает!

В завершение

Иногда, чтобы сделать правильную вещь — нужно сделать вещь не очень правильную, в моем случае это было переизобретением колеса в лице собственного JSON-парсера. Но мне хочется верить, что оно того стоит.

Кстати, кроме доступа к исходному тексту JSON-значения из документа, полифил также добавляет доступ к пути из ключей соответствующему каждому конкретному значению, это позволяет существенно упростить парсинг документов и избежать повторного обхода всех значений объекта. В дополнение, парсер имеет фикс безопасности, который применяется, в частности, в Fastify.

Ну, а если Вам нужно еще больше гибкости и удобства при парсинге JSON-документов, то обратите внимание на мою дополнительную библиотеку, которая позволяет матчить значения по их путям используя некое подобие глобов.

Ознакомиться с документацией и примерами всех библиотек можно в репозитории на GitHub. Также не стесняйтесь делиться собственным мнением и задавать вопросы в комментариях.

Удачного парсинга! Теперь ни одно подлое число от Вас не сбежит!

Понравилась статья?

Если статья показалась Вам полезной, пожалуйста, поставьте звезду на GitHub.

Также, приглашаю на мой новый канал по JavaScript-разработке в Telegram. Весь новый контент я выкладываю там.

Вопросы

  1. Какие есть недостатки при использовании этого полифила?

Сам полифил не очень большой, всего 8 КБ несжатого кода. Однако, нужно понимать, что он будет работать медленнее, чем нативная реализация. В 25 раз медленнее, если быть точным. Но, данный парсер позволяет пропускать через себя 1 МБ вложенных JSON-данных за ~40 миллисекунд. Этого должно быть вполне достаточно для большинства приложений.

Также, полифил использует кастомный парсер только в том случае, когда Вы применяете функционал из нового стандарта (аргумент context в reviver-функции), во всех остальных случаях используется обычная нативная реализация.

  1. Написать свой парсер не такая простая задача, он точно работает корректно?

Вся прелесть JSON в том, что этот формат имеет очень простую спецификацию. Также, парсер хорошо покрыт unit-тестами и протестирован на большом количестве реальных сложных документов. Код парсера написан на предельно строгом TypeScript, а сгенерированные бандлы проверяются перед каждым релизом как вручную, так и автоматически на предмет безопасности. Также, библиотека имеет ноль зависимостей и включает дополнительные фичи повышающие безопасность. Таким образом, библиотека может быть использована даже в проектах с достаточно высокими требованиями к безопасности.

Подробнее про безопасность в npm и выбор зависимостей Вы можете почитать в моем цикле статей на Хабре.

  1. Как лучше представлять большие числа в JSON-документах?

Стандарт JSON не накладывает каких-либо ограничений на размер чисел, однако, авторы некоторых API часто представляют большие числа как строки в своих JSON-документах:

{ "foo": "123456789123456789123" }

Данный подход позволяет JS парсить большие числа используя нативную реализацию.

Однако, я абсолютно убежден, что правильным решением является использование семантически-корректного типа для представления больших чисел в JSON — number. Это не вина стандарта JSON и других платформ в том, что разработчики EcmaScript оказались столь недальновидными. Ровно по этой причине комитет TC39 работает над внедрением нового стандарта, а я разработал описанные выше библиотеки.

P/S: в комментариях поделились интересным кейсом — если Ваш API имеет прослойки между сервером и клиентом, то представление данных стоит внимательно проверять, бывают случаи, когда какой-нибудь API Gateway может "портить" числа в документах.




Комментарии (59):

  1. funca
    /#25151672 / +10

    Внезапно неперевод. Очень крутая работа в плане проекта, документации и продвижения.

  2. incogn1too
    /#25151682 / +13

    "современные языки программирования" - улыбнуло. Внезапно в 70-ых lisp был в состоянии решить такие проблемы.

    • funca
      /#25151762 / +3

      Внезапно в 70-ых перед lisp такой проблемы даже не стояло: формат JSON (JavaScript Object Notation) появился только спустя 30 с лишним лет, - когда lisp уже успел обрести просветление и превратиться в миф.

      • incogn1too
        /#25151776 / +8

        Не будьте столь категоричны. Во первых - есть реализации json-a под lisp. Во вторых - проблема не в джейсоне, как в таковом. Просто, в случае lispa - математику не хипстеры писали.

        • fominslava
          /#25151780 / +1

          А как в Lisp представляются большие числа? Python вот не имеет ограничений в размере чисел, но нужно понимать, что это всегда компромисс, ведь такая реализация всегда будет работать существенно медленнее, чем когда число целиком влезает в регистр процессора.

        • klimkinMD
          /#25155826

          За хипстеров не скажу, но недавно в обсуждении провели проверку вычисления современными средствами (языками, калькуляторами и т.д.) тангенса гугола (tg(10^100)), порядка десятка инструментов бодро выдали ответ(!), результаты получились от -5 до +6. И расстраивает то, что ни один не намекнул, что с такими числами обращаться не умеет.

          • DWM
            /#25162686

            А тангенсы в градусах или в радианах брали и возвращали значения?

            А что на это говорила документация (языков, калькуляторов и т.д.)?

            В JS говорит что нельзя смешивать BigInt с другими типами. Или говорит что нельзя конвертировать нецелые числа в BigInt.(чтобы конвертировать градусы в радианы).

        • Dimaasik
          /#25161352

          Мне кажется что в мире программирования есть реализация чего угодно под что угодно)

        • demimurych
          /#25161956 / +2

          Не несите чуши. Математика в JS как и в большинстве современных языков программирования реализована согласно стандарту IEEE 754.

  3. funca
    /#25151714 / +12

    Я в свое время обжёгся с этим bigint. Клиент и сервер на python где поддержка больших чисел уже много лет из коробки. Сериализация и десериализация JSON на тестах работает четко. Но в продакшн между ними оказался API Gateway от известного вендора, со скриптигном на JS, вытворявший все те непотребства, что описаны в статье. В итоге все переделали на строки.

    В спецификации JSON не уточняются ограничения на размер целых чисел - интерпретация отдана на откуп реализации. Пройдет ещё куча времени, пока все о чем-нибудь договорятся и подстроятся.

    Как PoC библиотека просто класс и думаю ей можно найти массу применений. Но гонять bigint в JSON я бы не рекомендовал.

    • fominslava
      /#25151722 / +2

      Отличный пример, спасибо, что поделились! Еще интересный момент, что Dev Tools в Google Chrome также показывает значения в отформатироанном JSON уже некорректно, что иногда сильно сбивает с толку :)

    • slonopotamus
      /#25152310 / -1

      Но гонять bigint в JSON я бы не рекомендовал.

      Допустим, мы делаем приложение в котором юеры могут лайкать фотки котиков. Какой тип данный вы будете использовать для подсчёта количества лайков? Строку, что ли?

      • KivApple
        /#25152330

        Так там и bigint не пригодится. Всё влезет в обычный number.

        • Homiakin
          /#25152772 / +9

          ок, ОЧЕНЬ милых котиков :)

          • Nikita22007
            /#25157668

            Верхний предел int64 со знаком примерно 10^19. Число людей в обозримом будущем врятли превысит 10^11. условимся, что на каждого человека приходится в среднем не более 100 аккаунтов. Тогда нам потребуется хранить число максимум 10^13, что даёт нам запас ещё на 6 порядков.

      • fireSparrow
        /#25153828 / +2

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

  4. hellamps
    /#25152018 / +2

    из рфц: This specification allows implementations to set limits on the range and precision of numbers accepted

    • fominslava
      /#25152932 / +1

      Справедливо, но дело в том, что другие ЯП дают возможность разработчику самому решить как парсить JSON, в этом случае "implementation" (из вашей отсылки) это код разработчика. А авторы EcmaScript решили это за нас и не оставили нам никакого выбора (строго наложив ограничения IEEE 754). Собственно по этому мы и работает над расширением стандарта :)

      • funca
        /#25153870 / +1

        По идее аналогичная проблема со множеством вариантов интерпретации существует и для строк. Но решается она сейчас указанием encoding. Возможно аналогичный механизм для number - с возможностью указать название формата представления чисел - больше бы соответствал общей концепции.

        Да, доступ к результатам работы токенайзера даёт ещё больше свободы. Например, я могу поставить себе парсер для которого правила интерпретации задаются какой-нибудь JSON Schema. Но это довольно специфическая задача, и мне кажется будет лучше иметь это в виде отдельного API, а не костылей к JSON.parse(). За одно там можно было бы предложить решение для парсинга потоковых форматов типа json-lines.

        • fominslava
          /#25154292 / +1

          Ну, костылём я бы это не назвал. Вполне себе логичное (и ожидаемое) расширение нативного API.

          А в чем сложность с json-lines? Сканируешь себе поток и скармливаешь строки парсеру постепенно. Не уверен что эту задачу в библиотеку стоит выделять, хотя, конечно, можно это сделать.

          • funca
            /#25154602 / +1

            Сканируешь себе поток и скармливаешь строки парсеру постепенно.

            А можно было бы проходить json-lines поток в один проход - без загрузки и буферизации каждой отдельной строки в памяти (они могут быть довольно длинными, что сказывается как на потреблении памяти так и на latency). Вот ещё кейс, когда по сути нужен лишь токенайзер, без парсинга: https://habr.com/ru/company/quadcode/blog/660229/ .

            Вариант, предложенный в статье, не выглядит эффектным: он все равно делает парсинг, даже когда это фактически не требуется. К тому же создаёт массу новых временных объектов. Поэтому я за отдельное API.

            • fominslava
              /#25154864 / +1

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

              А что Вы имеете ввиду под "все равно делает парсинг, даже когда это фактически не требуется" и "создаёт массу новых временных объектов"? Не совсем понятно.

              • funca
                /#25155114 / +2

                JSON.parse(

                '{ "foo": 123456789123456789123 }',

                (key, value, context) => (

                (key === 'foo' ? BigInt(context.source) : value)

                )

                );

                Здесь для "foo" value не используется, однако парсер его все равно парсит, создаёт и передаёт в колбек, как велит интерфейс функции JSON.parse(). А если мы пишем, например, санитайзер - как делали ребята по ссылке в комменте выше, когда интересуют только ключи - то парсить value не требуется вообще - нужны лишь данные от токенайзера (в вашем примере key и context.source). Получается парсер делает кучу ненужной работы.

                Чтобы передать в callback значение context, в вашей редакции каждый раз создаётся новый объект. По одному разу на каждую ноду.

                Причина у проблем общая - добавление ортогональной по сути фичи в интерфейс JSON.parse().

                • fominslava
                  /#25158736 / +1

                  Ну да, накладные расходы, конечно, есть. Хотя мне сложно представить ситуацию и объемы данных когда это было бы критично. Вероятно для таких исключительных ситуаций можно написать/форкнуть собственное супер-оптимизированное решение.

                  • funca
                    /#25159086

                    Интересно, в каких ещё ситуациях может потребоваться доступ к сырым строкам? В JSON разных типов данных не много. Тема с number / bigint, как видно, отпадает - надёжнее передавать сразу строкой. string и так строки. Из примитивных остаются только "true", "false" и null.

  5. AlexXYZ
    /#25152136 / -1

    А можно ли передать в JSON зацикленные структуры или когда некоторые структуры являются ссылочными, а JSON стрингует их как отдельные? Есть какие то методы?

    • aamonster
      /#25152216

      Нельзя (по стандарту). Но есть библиотеки для сериализации (только это уже не JSON).

    • Alexandroppolus
      /#25152222

      Из коробки - нет. Придется вручную велосипедить, например, нормализовывать объект, придумывать некую систему идентификаторов (внешних ключей), и т.д.

      • AlexXYZ
        /#25152500 / -5

        Уже пришлось поколхозить. Сколько лет JSON существует, а без колхоза никуда.

        • dopusteam
          /#25154232

          Он просто не для циклических структур, берите что то подходящее и не колхозьте

    • aamonster
      /#25152346

      Пример либы (в описании как раз излагается принцип): https://github.com/WebReflection/flatted

      • AlexXYZ
        /#25152476 / -1

        Спасибо, глянул. Но только потом подумал, что приёмниками такого «нового» формата могут быть только клиенты JS. C# NewtonSoft JSON не поддерживает ссылки внутри Json. Шьорт побьери. Эта их жажда угодить клиенту, сделав ключ .Parent, чего в обычном JS точно нет, сломала возможность делать множественные ссылки.

        • aamonster
          /#25152736

          Ну, портировать либу на другие языки нетрудно.

          Но если у вас проблема только с ключом .Parent – то проще всего сериализовать без него (на js – в JSON.serialize передать скипающую его функцию), а после десериализации обходить дерево, восстанавливая его.

          Но JSON – это всё-таки для "чистых данных", а не формат сериализации общего назначения.

        • buldo
          /#25155056 / +1

          В каком смысле newtonsoft не поддерживает ссылки? Мы гоняем через него объекты с циклическими ссылками внутри

    • sinkapoy
      /#25152858 / +1

      Нельзя передать зацикленную структуру, т.к. сама спецификация не поддерживает ссылки. Также нельзя просто скопировать объект, т.к. это ведет к бесконечному копированию.

      Если бы у меня стояла ОСТРАЯ нужда, то можно сделать проход по дереву объектов в поисках ссылок на элементы структуры, после этого создать отдельное поле в JSON с иерархией и путем до оригиналов. После десериализации просто восстанавливать полное представление. У Articy Draft в экспорте json есть модели объектов и иерархия, можно попробовать такой подход тоже.
      Как вариант, можно добавить префикс для строк, после которого вставлять путь до оригинала. В любом случае так лучше не делать)

    • mekegi
      /#25153524 / +5

      Уже было в симпсонах. Авторы YAML решили предусмотреть и зацикленность ссылки и еще тысячу других няшных фич. В итоге вырастили кактус который теперь приходится всем жрать https://noyaml.com/

  6. PsyHaSTe
    /#25153846 / +5

    Для себя решил просто передавать цифры строками и не париться на тему полифиллов и не париться. Вот что на эту тему говорит стандарт:


    This specification allows implementations to set limits on the range
    and precision of numbers accepted. Since software that implements
    IEEE 754-2008 binary64 (double precision) numbers [IEEE754] is
    generally available and widely used, good interoperability can be
    achieved by implementations that expect no more precision or range
    than these provide, in the sense that implementations will
    approximate JSON numbers within the expected precision. A JSON
    number such as 1E400 or 3.141592653589793238462643383279 may indicate
    potential interoperability problems, since it suggests that the
    software that created it expects receiving software to have greater
    capabilities for numeric magnitude and precision than is widely
    available.

    То есть, хотя стандарт прямо не запрещает, он не рекомендуют использовать числа которые не могут быть точно представлены как IEE754 double.




    Хех, funca описывает ровно то что я хотел избежать, но у меня это удалось и я с проблемами не столкнулся.

    • formerchild
      /#25157458 / +1

      Наверное, все так и делают. Мы тоже бигнамбы строками передаем

  7. sshmakov
    /#25154270 / +3

    Забыли упомянуть, что в JavaScript под integer не 64 бита, а 53.

    64 - это в Java.

  8. EvgeniiKh
    /#25154302

    JSON является одним из очень простых, но в то же время эффективных языков для хранения и передачи данных.

    JSON - это текстовый формат, а не язык.

    • Arqwer
      /#25154492 / +3

      Формальный язык - множество конечных слов над конечным алфавитом. Это гораздо более широкое понятие, чем вам кажется. Например, множество всех полиндромов это тоже язык. И множество всех простых чисел в двоичном представлении - это язык. Если вы можете к чему-то составить грамматику, то это точно язык. Любой текстовый формат это тоже язык.

      Формальный язык, Википедия

    • kisskin
      /#25154582 / +1

      И в добавок - эффективным будет бинарный формат, а json явно на порядок медленнее и обьемнее

  9. FlashManiac
    /#25156104

    Именно по этому я тоже написал свою библиотеку. Но я пошел дальше - JS/JSON объекты кодируются/декодируются в бинарный формат с различными оптимизациями. Есть поддержка всех базовых JS типов включая новые BigInt/Symbol. Все работает как в браузере так и на nodeJS, а так же покрыто 100% тестами. Если нужно добавить уникальные кастомные типы - это тоже есть.
    https://github.com/superman2211/xobj/tree/master/packages/core

    • Alexandroppolus
      /#25156228 / +2

      А во что сериализуется symbol?

      По поводу функций - если они содержат замыкание, то правильная сериализация невозможна (бесполезно такую функцию приводить к строке).

      • mayorovp
        /#25156436

        Судя по его тестам, символ сериализуется во что-то вроде уникального идентификатора символа в пределах сообщения. После десериализации сохраняется взаимная идентичность символов, но теряется связь символов с другими частями программы. Думаю, такое поведение можно назвать ожидаемым и очевидным, хотя я не представляю зачем вообще оно нужно.


        А вот с функциями вопрос интереснее...

        • Alexandroppolus
          /#25156558

          хотя я не представляю зачем вообще оно нужно

          Оно имело бы некоторый смысл, если бы в сериализации объектов поддерживались ключи-символы. Но автор случайно забыл про Object.getOwnPropertySymbols...

          А то что при десереализации создаются новые значения символов, прямо такое себе. Всё-таки символы - примитивные значения, и по аналогии с другими примитивами должны восстанавливаться. Возможно, стоит добавить некий глобальный словарь символов для подстановки.

          • mayorovp
            /#25156656

            Вот как раз ключи-символы при сериализации должны игнорироваться, на неперечислимость символов полагается слишком много библиотек. Сериализую какую-нибудь вью-модель вы, как правило, не хотите включать в сериализацию всех её наблюдателей включая DOM.


            Всё-таки символы — примитивные значения, и по аналогии с другими примитивами должны восстанавливаться.

            Но невозможность восстановить уникальный символ — это ключевое и общеизвестное свойство символов.

        • FlashManiac
          /#25156986 / +1

          Да вы правы - символ уникален в пределах сообщения. Symbol как значение передать в принципе нельзя.

        • FlashManiac
          /#25156998

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

          • FlashManiac
            /#25157002

            Аналогично можно и символами сделать ) Создавать некую таблицу.

          • Alexandroppolus
            /#25157672 / +1

            И они должны быть строго анонимные

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

            const func = ((x) => () => x)(123);

            func() всегда возвращает 123

            func.toString() дает строку "() => x". Очевидно, что десериализованная из такой строки функция совсем необязательно будет возвращать 123.

            • funca
              /#25159142

              Честно говоря удивлен, но оказалось что-то подобное уже есть https://www.npmjs.com/package/serialize-closures. Причем сделано снаружи, а не изнутри движка (так-то DevTools показывают внутренности объектов, поэтому вопрос сериализации замыканий это лишь вопрос доступа к соответствующим API).

              • mayorovp
                /#25159854

                Не, движок и его отладочные интерфейсы тут вообще ни при чём:


                Note: only functions whose code has first been processed by ts-closure-transform are eligible for serialization.

  10. demimurych
    /#25162434

    Начать следовало бы с того - как расшифровывается JSON: JavaScript Object Notation

    Уже с єтого момента, должен был бы сработать маячек на тему, а не связаны ли особенности, которые я обсуждаю в рамках своей заметки чем-то фундаментальным, что связано с самим языком?

    Открыв официальную спецификаци, языка JavaScript, а именно главу касающуюся парсинга переданной строки в метод обьекта JSON https://tc39.es/ecma262/multipage/structured-data.html#sec-json.parse, мы должны были бы обратить внимание на строку алгоритма парсинга:

    Let completion be Completion(Evaluation of script).

    которая снимает ровно все вопросы, которые бы мог бы себе задать программист языка JS, а именно что будет если я подсуну некорректные с точки зрения синтаксиса языка данные и получить испчерпывающий ответ на єтот вопрос - произойдет ровно тоже самое, что должно произойти былобы если бы Вы тоже самое сделали используя обычный (не json) код JS.

    Я єто к тому, что зачастую, как в єтой статье, неожиданное ыоведение тех или иных частей языка связано не с "особенностями" языка, а с тем, что мы про него не знаем, так как учим его по mdm, блог постам и прочей "фигне" отличающейся от первоисточника (спецификации) как єскимос от папуаса.

    То есть: JSON.parse работает строго в рамках того как он и доажен работать и решение "заявлрнной" проблемы лежит не в плоскости изменения метода parse, который не является ничем иным как официальным (єталонным) алгоритмом парсинга JS кода), но только в введение отдельного апи в рамках host системы которая сделает работу за Вас.