При интеграции систем часто возникает ситуация, когда внешнее приложение (написанное на таких строго типизированных языках, как Java, C#, Go или C++) предъявляет жесткие требования к типам данных. Если схема данных (например, Swagger или OpenAPI) ожидает вещественное число float или double, то значение обязательно должно содержать десятичную точку. Если передать просто целое число, принимающий парсер выдаст ошибку валидации.
Разработчики на платформе 1С регулярно сталкиваются со следующей проблемой: при использовании стандартного механизма ЗаписатьJSON платформа автоматически оптимизирует числа. Если у числа с плавающей точкой нулевая дробная часть (например, 10.00), 1С сериализует его в JSON как обычное целое число — 10. Попытки преобразовать число в строку с помощью функции Формат приводят к тому, что в JSON-файле значение заключается в кавычки ("10.00"), что превращает его в текстовый тип данных, а это также приводит к ошибке десериализации на стороне внешней системы.
В этой статье мы подробно рассмотрим, почему так происходит, проанализируем различные подходы к решению этой задачи и разберем несколько практических способов — от штатных и элегантных до обходных приемов (лайфхаков), проверенных разработчиками на практике.
Платформа 1С:Предприятие при сериализации объектов в формат JSON стремится максимально оптимизировать размер результирующего пакета данных. Числовой тип в 1С является универсальным. На внутреннем уровне платформа сама решает, как представить число в текстовом файле обмена. Если у числа отсутствует значащая дробная часть, то десятичная точка и последующие нули отбрасываются за ненадобностью с точки зрения стандартов JSON. Однако спецификации некоторых внешних API требуют строгого соблюдения формата. Выясним, какими путями мы можем обойти это поведение платформы.
Это самый изящный, быстрый и абсолютно штатный способ решения проблемы. Вместо использования высокоуровневой функции ЗаписатьJSON, которая работает по принципу «черного ящика», мы можем использовать объект ЗаписьJSON для ручного контроля записи элементов потока.
В объекте ЗаписьJSON существует метод ЗаписатьБезОбработки() (в англоязычном синониме — WriteRaw). Этот метод позволяет вставить в JSON-поток любую произвольную строку «как есть», без экранирования, валидации и автоматического оборачивания в кавычки. С его помощью мы можем самостоятельно отформатировать число программно и записать его в файл как числовое значение.
Рассмотрим подробнее пример кода, реализующий этот подход:
// Создаем объект записи JSON
ЗаписьJSON = Новый ЗаписьJSON;
ПараметрыЗаписи = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет, " ", Истина);
ЗаписьJSON.УстановитьСтроку(ПараметрыЗаписи);
// Открываем корневой объект JSON
ЗаписьJSON.ЗаписатьНачалоОбъекта();
// Записываем стандартное текстовое свойство
ЗаписьJSON.ЗаписатьИмяСвойства("ИмяДетали");
ЗаписьJSON.ЗаписатьЗначение("Шестерня ведущая");
// Нам нужно записать целое число 450 как вещественное "450.00"
МоеЧисло = 450;
// Шаг 1: Форматируем число в строку с точкой в качестве разделителя и принудительными нулями
// ЧДЦ=2 указывает, что всегда должно быть 2 знака после запятой
// ЧРД=. устанавливает точку в качестве разделителя дробной части
// ЧГ= отключает группировку разрядов пробелами (чтобы не получить "450 000")
СтрокаЧисла = Формат(МоеЧисло, "ЧДЦ=2; ЧГ=; ЧРД=.");
// Шаг 2: Записываем имя свойства
ЗаписьJSON.ЗаписатьИмяСвойства("Цена");
// Шаг 3: Записываем подготовленную строку напрямую в поток как сырое число без кавычек
ЗаписьJSON.ЗаписатьБезОбработки(СтрокаЧисла);
// Закрываем объект JSON
ЗаписьJSON.ЗаписатьКонецОбъекта();
// Получаем результат
РезультатJSON = ЗаписьJSON.Закрыть();
В результате работы данного алгоритма мы получим абсолютно валидную строку JSON:
{"ИмяДетали": "Шестерня ведущая", "Цена": 450.00}
Обратите внимание, что значение свойства "Цена" записано без кавычек, но при этом сохранило дробную часть. Это идеальное решение для высоконагруженных интеграций, так как оно не требует повторного разбора строки или сложных манипуляций с памятью.
Малоизвестная, но очень полезная возможность платформы скрывается в параметрах метода ЗаписатьЗначение() объекта ЗаписьJSON. Данный метод принимает несколько параметров, вторым из которых является булево свойство ЧислоКакВещественное (или в англоязычном варианте NumberAsReal).
Если передать в этот параметр значение Истина, платформа принудительно запишет переданное число в экспоненциальной нотации (научном формате представления чисел).
Посмотрим на пример реализации данного метода:
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписьJSON.ЗаписатьНачалоОбъекта();
ЗаписьJSON.ЗаписатьИмяСвойства("ОбъемПоставки");
// Передаем число 10 и указываем Истина для записи в виде вещественного числа
ЗаписьJSON.ЗаписатьЗначение(10, Истина);
ЗаписьJSON.ЗаписатьКонецОбъекта();
РезультатJSON = ЗаписьJSON.Закрыть();
В результате выполнения этого кода платформа сформирует следующий JSON:
{"ОбъемПоставки":1E1} (или 1.0E1 в зависимости от конкретной версии платформы).
Важное примечание: Большинство современных стандартизированных парсеров во внешних системах (например, библиотеки десериализации в Java/Jackson, C#/.NET JSON, Go encoding/json) корректно распознают экспоненциальную запись 1E1 как число с плавающей точкой (тип double или float). Перед внедрением этого способа обязательно протестируйте совместимость с принимающей стороной. Если принимающий парсер поддерживает экспоненциальный формат, это избавит вас от необходимости форматировать строки вручную.
Если структура вашего JSON-документа очень сложная, содержит множество вложенных массивов и соответствий, то собирать его вручную через методы потока ЗаписьJSON может быть трудоемко. В таких случаях разработчики часто прибегают к быстрому сериализатору ЗаписатьJSON(), передавая в него сразу всю структуру данных.
Но как заставить его сохранить вещественность чисел? На форуме было предложено оригинальное и простое решение: добавлять к целым числам искусственную дробную часть-маркер, которая гарантированно не округлится платформой, а затем выполнять пост-обработку полученной текстовой строки.
Разберем этот алгоритм по шагам:
0.987654321.ЗаписатьJSON.987654321 на нули.Посмотрим на пример кода, реализующий эту логику:
// Создаем сложную структуру данных
ДанныеДляОтправки = Новый Структура;
ДанныеДляОтправки.Вставить("Идентификатор", 101); // Это поле должно остаться целым
// Добавляем маркер к числу, которое должно быть вещественным (например, 25.00)
ЦелоеЧислоДляПреобразования = 25;
ЧислоСМаркером = ЦелоеЧислоДляПреобразования + 0.987654321;
ДанныеДляОтправки.Вставить("Вес", ЧислоСМаркером);
// Быстрая сериализация структуры в JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ДанныеДляОтправки);
ИсходныйJSON = ЗаписьJSON.Закрыть();
// Выполняем автозамену маркера на корректный дробный хвост
// Заменяем ".987654321" на ".00"
ИтоговыйJSON = СтрЗаменить(ИсходныйJSON, ".987654321", ".00");
В результате мы получим строку:
{"Идентификатор":101,"Вес":25.00}
Преимущества метода: Сохраняется простота и высокая скорость сериализации больших объемов данных стандартным методом платформы.
Недостатки метода: Существует риск возникновения ошибок округления вещественных чисел при сложении на стороне 1С (из-за особенностей представления чисел с плавающей точкой в памяти компьютера). Также этот метод требует аккуратного подбора уникальной последовательности цифр для маркера, чтобы случайно не заменить реальные данные в других полях JSON.
Если вы хотите избежать рисков, связанных с использованием маркеров из Способа 3, но вам по-прежнему необходимо обрабатывать готовую JSON-строку после стандартной сериализации, можно использовать регулярные выражения. Это позволит находить только конкретные поля по их именам (ключам) в структуре JSON и принудительно дописывать к их целым значениям дробную часть.
Для реализации регулярных выражений в операционных системах Windows можно использовать COM-объект VBScript.RegExp.
Рассмотрим пример кода:
// Исходная строка JSON после стандартной сериализации
// Предположим, поле "Сумма" записалось как целое число: {"Имя":"Товар","Сумма":150,"Количество":5}
ИсходныйJSON = "{""Имя"":""Товар"",""Сумма"":150,""Количество"":5}";
// Инициализируем объект регулярных выражений
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.Global = Истина;
RegExp.IgnoreCase = Истина;
// Паттерн ищет ключ "Сумма", двоеточие и идущее за ним целое число, у которого нет десятичной точки
// (""Сумма""\s*:\s*) - группа 1 (имя ключа и двоеточие с возможными пробелами)
// (\d+) - группа 2 (последовательность цифр целого числа)
// (?!\.) - негативный просмотр вперед (проверяет, что далее нет точки)
RegExp.Pattern = "(""Сумма""\s*:\s*)(\d+)(?!\.)";
// Заменяем найденное значение, подставляя обратно имя ключа, целое число и дописывая к нему ".00"
// $1 - ссылка на первую группу захвата
// $2 - ссылка на вторую группу захвата
РезультатJSON = RegExp.Replace(ИсходныйJSON, "$1$2.00");
После выполнения регулярного выражения переменная РезультатJSON примет вид:
{"Имя":"Товар","Сумма":150.00,"Количество":5}
Этот способ является максимально надежным и безопасным при пост-обработке готовых JSON-пакетов, так как изменения вносятся точечно и строго по именам ключей, не затрагивая другие числовые поля (например, целочисленные идентификаторы или количества).
Мы проанализировали четыре практических подхода к решению проблемы записи целых чисел как вещественных в JSON на платформе 1С: