При работе с данными в 1С часто возникает необходимость экспортировать их в формат JSON для обмена с внешними системами или хранения. Однако при попытке записать последовательность объектов в JSON-файл мы можем столкнуться с проблемой, когда результирующий файл оказывается невалидным, содержащим синтаксические ошибки, такие как лишние запятые или некорректная структура. В этой статье мы подробно разберем, почему возникает такая ситуация и как правильно формировать JSON-массивы из множества объектов в 1С, используя наиболее эффективные подходы.
Начнем с рассмотрения типичного кода, который приводит к некорректной записи JSON-файла, как это было описано в сообщении пользователя:
Функция Создатьjson()
ЗаписьДж = Новый ЗаписьJSON;
ЗаписьДж.ПроверятьСтруктуру = Ложь; // Здесь может скрываться корень проблемы
ПараметрыЗаписиДж = Новый ПараметрыЗаписиJSON( , Символы.Таб);
ЗаписьДж.ОткрытьФайл("C:\ФайлВыгрузки.json",,,ПараметрыЗаписиДж);
// ... Запрос к базе данных и выборка данных ...
Пока ВыборкаДетальныеЗаписи.Следующий Цикл
Структура1 = новый Структура;
Структура1.Вставить("productName" , Строка(ВыборкаДетальныеЗаписи.Наименование));
// ... Заполнение других свойств структуры ...
ЗаписатьJSON(ЗаписьДж, Структура1); // Повторный вызов ЗаписатьJSON внутри цикла
КонецЦикла;
ЗаписьДж.Закрыть;
КонецФункции
Результатом выполнения такого кода является файл (для анализа которого удобно использовать просмотр JSON с форматированием), который выглядит примерно так:
{ "productName": "Оборудование (объекты основных средств)", "code": "00-00000004" }, {, "productName": , "Оборудование к установке", "code": , "00-00000005" },
или даже так:
{ "productName": ..., "code": ... }{ "productName": ..., "code": ... }
Это не является валидным JSON-файлом. Согласно стандарту JSON, корневой элемент документа должен быть либо одним объектом (заключенным в фигурные скобки {}), либо одним массивом (заключенным в квадратные скобки []). В нашем случае, многократный вызов метода ЗаписатьJSON(ЗаписьДж, Структура1) внутри цикла приводит к тому, что в файл записываются несколько независимых JSON-объектов, конкатенированных друг за другом. Это нарушает синтаксис JSON.
ЗаписьJSON.ПроверятьСтруктуруОбратите внимание на строку ЗаписьДж.ПроверятьСтруктуру = Ложь; в исходном коде. Это свойство объекта ЗаписьJSON по умолчанию имеет значение Истина. Когда оно установлено в Истина, платформа 1С автоматически проверяет валидность формируемой JSON-структуры. Если структура нарушена (например, вы пытаетесь записать несколько корневых объектов), генерируется исключение, и процесс записи останавливается.
Установка ЗаписьДж.ПроверятьСтруктуру = Ложь; отключает эту проверку. Это позволяет системе "проглотить" синтаксическую ошибку и записать невалидный JSON без генерации исключения, что, казалось бы, "решает" проблему, но на самом деле лишь скрывает ее. В результате мы получаем битый файл. Рекомендуем всегда держать это свойство в Истина во время разработки и отладки, чтобы своевременно выявлять ошибки структуры. Отключать его следует только в случаях, когда вы абсолютно уверены в корректности формируемого JSON и вам нужна небольшая оптимизация производительности при работе с очень большими объемами данных.
Рассмотрим два основных способа, которые позволяют корректно сформировать JSON-массив из коллекции объектов в 1С.
Массив для сбора данныхНаиболее простой и часто рекомендуемый способ — это сначала собрать все структуры, которые должны стать элементами JSON-массива, в один объект Массив 1С, а затем вызвать метод ЗаписатьJSON() один раз, передав ему этот собранный массив. Объект ЗаписьJSON самостоятельно сериализует Массив 1С в корректный JSON-массив.
Разберем этот подход по шагам:
Массив: Этот массив будет хранить все наши структуры, которые в конечном итоге станут элементами JSON-массива.Массив структурами: В цикле мы создаем каждую структуру (представляющую один JSON-объект) и добавляем ее в созданный Массив.ЗаписатьJSON() один раз: После того как все структуры собраны в Массив, мы вызываем метод ЗаписатьJSON(), передавая ему этот Массив.Пример кода:
Функция СоздатьjsonКорректноЧерезМассив()
ЗаписьДж = Новый ЗаписьJSON;
ЗаписьДж.ПроверятьСтруктуру = Истина; // Рекомендуется для отладки
ПараметрыЗаписиДж = Новый ПараметрыЗаписиJSON( , Символы.Таб); // Параметры форматирования для читаемости
ЗаписьДж.ОткрытьФайл("C:\ФайлВыгрузки.json",,,ПараметрыЗаписиДж);
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.ЕдиницаИзмерения.Наименование КАК ЕдиницаИзмеренияНаименование,
| Номенклатура.ВидНоменклатуры.Наименование КАК ВидНоменклатурыНаименование,
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.СтавкаНДС.Наименование КАК СтавкаНДСНаименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
ДанныеДляJSON = Новый Массив; // Создаем объект Массив для сбора всех структур
Пока ВыборкаДетальныеЗаписи.Следующий Цикл
Структура1 = новый Структура;
Структура1.Вставить("productName" , Строка(ВыборкаДетальныеЗаписи.Наименование));
Если Не ВыборкаДетальныеЗаписи.Артикул = Null Тогда
Структура1.Вставить("articul" , Строка(ВыборкаДетальныеЗаписи.Артикул));
Иначе
Структура1.Вставить("articul", ""); // Важно: всегда указывать значение, даже пустое, чтобы избежать ", "key": ,"
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи.ЕдиницаИзмеренияНаименование = Null Тогда
Структура1.Вставить("package" , Строка(ВыборкаДетальныеЗаписи.ЕдиницаИзмеренияНаименование));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи.ВидНоменклатурыНаименование = Null Тогда
Структура1.Вставить("vid" , Строка(ВыборкаДетальныеЗаписи.ВидНоменклатурыНаименование));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи.СтавкаНДСНаименование = Null Тогда
Структура1.Вставить("nds" , Строка(ВыборкаДетальныеЗаписи.СтавкаНДСНаименование));
КонецЕсли;
// Дополнительные запросы для сбора данных по остаткам и ценам
Запрос1 = Новый Запрос;
Запрос1.Текст = "ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура.Ссылка КАК НоменклатураСсылка,
| ТоварыНаСкладахОстатки.Склад.Наименование КАК СкладНаименование,
| ТоварыНаСкладахОстатки.ВНаличииОстаток КАК ВНаличииОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
|ГДЕ
| ТоварыНаСкладахОстатки.Номенклатура.Ссылка = &Ссылка";
Запрос1.УстановитьПараметр("Ссылка", ВыборкаДетальныеЗаписи.Ссылка);
РезультатЗапроса1 = Запрос1.Выполнить();
ВыборкаДетальныеЗаписи1 = РезультатЗапроса1.Выбрать();
// Важно: если ожидается несколько остатков по разным складам,
// то Ostatok и sklad должны быть массивом объектов внутри Структура1.
// Для упрощения примера, возьмем только первый найденный или один объект.
// Если же должно быть несколько остатков, нужно формировать массив.
// Например:
// МассивОстатков = Новый Массив;
// Пока ВыборкаДетальныеЗаписи1.Следующий Цикл
// СтруктураОстатка = Новый Структура;
// СтруктураОстатка.Вставить("Количество", Строка(ВыборкаДетальныеЗаписи1.ВНаличииОстаток));
// СтруктураОстатка.Вставить("Склад", Строка(ВыборкаДетальныеЗаписи1.СкладНаименование));
// МассивОстатков.Добавить(СтруктураОстатка);
// КонецЦикла;
// Если МассивОстатков.Количество() > 0 Тогда
// Структура1.Вставить("stocks", МассивОстатков);
// КонецЕсли;
Если ВыборкаДетальныеЗаписи1.Следующий() Тогда // Только первый остаток для примера
Если Не ВыборкаДетальныеЗаписи1.ВНаличииОстаток = Null Тогда
Структура1.Вставить("Ostatok" , Строка(ВыборкаДетальныеЗаписи1.ВНаличииОстаток));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи1.СкладНаименование = Null Тогда
Структура1.Вставить("sklad" , Строка(ВыборкаДетальныеЗаписи1.СкладНаименование));
КонецЕсли;
КонецЕсли;
Запрос2 = Новый Запрос;
Запрос2.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1
| ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,
| ЦеныНоменклатурыСрезПоследних.ВидЦены.Наименование КАК ВидЦеныНаименование,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена,
| ЦеныНоменклатурыСрезПоследних.Валюта.Наименование КАК ВалютаНаименование
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК ЦеныНоменклатурыСрезПоследних
|ГДЕ
| ЦеныНоменклатурыСрезПоследних.Номенклатура.Ссылка = &Ссылка
|
|УПОРЯДОЧИТЬ ПО
| ЦеныНоменклатурыСрезПоследних.Период УБЫВ";
Запрос2.УстановитьПараметр("Ссылка", ВыборкаДетальныеЗаписи.Ссылка);
РезультатЗапроса2 = Запрос2.Выполнить();
ВыборкаДетальныеЗаписи2 = РезультатЗапроса2.Выбрать();
Если ВыборкаДетальныеЗаписи2.Следующий() Тогда // Только первая цена для примера
Если Не ВыборкаДетальныеЗаписи2.Цена = Null Тогда
Структура1.Вставить("price" , Строка(ВыборкаДетальныеЗаписи2.Цена));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи2.ВидЦеныНаименование = Null Тогда
Структура1.Вставить("vidPrice" , Строка(ВыборкаДетальныеЗаписи2.ВидЦеныНаименование));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи2.ВалютаНаименование = Null Тогда
Структура1.Вставить("Valuta" , Строка(ВыборкаДетальныеЗаписи2.ВалютаНаименование));
КонецЕсли;
КонецЕсли;
ДанныеДляJSON.Добавить(Структура1); // Добавляем каждую структуру в Массив
КонецЦикла;
ЗаписатьJSON(ЗаписьДж, ДанныеДляJSON); // Однократный вызов ЗаписатьJSON для всего Массива
ЗаписьДж.Закрыть;
КонецФункции
Этот подход гарантирует, что на выходе мы получим корректный JSON-массив, который будет выглядеть так:
[ { "productName": "Шкаф мед. ...", "articul": "", ... }, { "productName": "Банкетка ...", "articul": "", ... } ]
Обратите внимание на то, что мы добавили проверку на Null значения и в случае Null для некоторых полей (например, артикула) вставляем пустую строку "". Это важно для предотвращения формирования записей типа ", "articul": ,", где после двоеточия нет значения, что также является синтаксической ошибкой JSON.
Второй способ используется, когда объем данных настолько велик, что их нецелесообразно полностью загружать в память в виде объекта Массив. В таких случаях мы используем методы потоковой записи объекта ЗаписьJSON, явно указывая начало и конец массива.
Разберем этот подход по шагам:
ЗаписьДж.ЗаписатьНачалоМассива(), который записывает открывающую квадратную скобку [.ЗаписьДж.ЗаписатьНачалоОбъекта(), ЗаписьДж.ЗаписатьИмяСвойства(), ЗаписьДж.ЗаписатьЗначение() для формирования каждого отдельного JSON-объекта. Завершаем объект вызовом ЗаписьДж.ЗаписатьКонецОбъекта().ЗаписьДж.ЗаписатьКонецМассива(), который записывает закрывающую квадратную скобку ].Пример кода:
Функция СоздатьjsonПотоково()
ЗаписьДж = Новый ЗаписьJSON;
ЗаписьДж.ПроверятьСтруктуру = Истина; // Рекомендуется
ПараметрыЗаписиДж = Новый ПараметрыЗаписиJSON( , Символы.Таб); // Параметры форматирования
ЗаписьДж.ОткрытьФайл("C:\ФайлВыгрузкиПотоково.json",,,ПараметрыЗаписиДж);
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.ЕдиницаИзмерения.Наименование КАК ЕдиницаИзмеренияНаименование,
| Номенклатура.ВидНоменклатуры.Наименование КАК ВидНоменклатурыНаименование,
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.СтавкаНДС.Наименование КАК СтавкаНДСНаименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
ЗаписьДж.ЗаписатьНачалоМассива(); // Начинаем JSON-массив
Пока ВыборкаДетальныеЗаписи.Следующий Цикл
ЗаписьДж.ЗаписатьНачалоОбъекта(); // Начинаем очередной JSON-объект
ЗаписьДж.ЗаписатьИмяСвойства("productName");
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.Наименование));
ЗаписьДж.ЗаписатьИмяСвойства("articul");
Если Не ВыборкаДетальныеЗаписи.Артикул = Null Тогда
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.Артикул));
Иначе
ЗаписьДж.ЗаписатьЗначение(""); // Пустая строка вместо Null
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи.ЕдиницаИзмеренияНаименование = Null Тогда
ЗаписьДж.ЗаписатьИмяСвойства("package");
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.ЕдиницаИзмеренияНаименование));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи.ВидНоменклатурыНаименование = Null Тогда
ЗаписьДж.ЗаписатьИмяСвойства("vid");
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.ВидНоменклатурыНаименование));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи.СтавкаНДСНаименование = Null Тогда
ЗаписьДж.ЗаписатьИмяСвойства("nds");
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.СтавкаНДСНаименование));
КонецЕсли;
// Вложенные данные (остатки, цены) также можно записывать потоково.
// Например, для остатков, если они представляют массив:
// ЗаписьДж.ЗаписатьИмяСвойства("stocks");
// ЗаписьДж.ЗаписатьНачалоМассива();
// ... цикл по остаткам ...
// ЗаписьДж.ЗаписатьНачалоОбъекта();
// ЗаписьДж.ЗаписатьИмяСвойства("Количество");
// ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи1.ВНаличииОстаток));
// ЗаписьДж.ЗаписатьКонецОбъекта();
// ...
// ЗаписьДж.ЗаписатьКонецМассива();
// Для упрощения примера, возьмем только первый найденный остаток/цену.
Запрос1 = Новый Запрос;
Запрос1.Текст = "ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура.Ссылка КАК НоменклатураСсылка,
| ТоварыНаСкладахОстатки.Склад.Наименование КАК СкладНаименование,
| ТоварыНаСкладахОстатки.ВНаличииОстаток КАК ВНаличииОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
|ГДЕ
| ТоварыНаСкладахОстатки.Номенклатура.Ссылка = &Ссылка";
Запрос1.УстановитьПараметр("Ссылка", ВыборкаДетальныеЗаписи.Ссылка);
РезультатЗапроса1 = Запрос1.Выполнить();
ВыборкаДетальныеЗаписи1 = РезультатЗапроса1.Выбрать();
Если ВыборкаДетальныеЗаписи1.Следующий() Тогда
Если Не ВыборкаДетальныеЗаписи1.ВНаличииОстаток = Null Тогда
ЗаписьДж.ЗаписатьИмяСвойства("Ostatok");
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи1.ВНаличииОстаток));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи1.СкладНаименование = Null Тогда
ЗаписьДж.ЗаписатьИмяСвойства("sklad");
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи1.СкладНаименование));
КонецЕсли;
КонецЕсли;
Запрос2 = Новый Запрос;
Запрос2.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1
| ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,
| ЦеныНоменклатурыСрезПоследних.ВидЦены.Наименование КАК ВидЦеныНаименование,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена,
| ЦеныНоменклатурыСрезПоследних.Валюта.Наименование КАК ВалютаНаименование
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК ЦеныНоменклатурыСрезПоследних
|ГДЕ
| ЦеныНоменклатурыСрезПоследних.Номенклатура.Ссылка = &Ссылка
|
|УПОРЯДОЧИТЬ ПО
| ЦеныНоменклатурыСрезПоследних.Период УБЫВ";
Запрос2.УстановитьПараметр("Ссылка", ВыборкаДетальныеЗаписи.Ссылка);
РезультатЗапроса2 = Запрос2.Выполнить();
ВыборкаДетальныеЗаписи2 = РезультатЗапроса2.Выбрать();
Если ВыборкаДетальныеЗаписи2.Следующий() Тогда
Если Не ВыборкаДетальныеЗаписи2.Цена = Null Тогда
ЗаписьДж.ЗаписатьИмяСвойства("price");
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи2.Цена));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи2.ВидЦеныНаименование = Null Тогда
ЗаписьДж.ЗаписатьИмяСвойства("vidPrice");
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи2.ВидЦеныНаименование));
КонецЕсли;
Если Не ВыборкаДетальныеЗаписи2.ВалютаНаименование = Null Тогда
ЗаписьДж.ЗаписатьИмяСвойства("Valuta");
ЗаписьДж.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи2.ВалютаНаименование));
КонецЕсли;
КонецЕсли;
ЗаписьДж.ЗаписатьКонецОбъекта(); // Завершаем текущий JSON-объект
КонецЦикла;
ЗаписьДж.ЗаписатьКонецМассива(); // Завершаем JSON-массив
ЗаписьДж.Закрыть();
КонецФункции
Оба этих метода позволяют получить валидный JSON-массив. Выбор между ними зависит от объема данных и предпочтений разработчика. Для большинства стандартных задач, когда объем данных не экстремально велик, использование объекта Массив (первый способ) является более простым и понятным.
Null и пустые значения: Всегда проверяйте значения на Null перед их записью в JSON. Если значение Null, то лучше явно записать пустую строку "", число 0, или логическое значение Ложь (false), в зависимости от ожидаемого типа данных. Это предотвратит ошибки форматирования типа "key": ,.Структура, Массив или использовать методы ЗаписатьНачалоОбъекта()/ЗаписатьКонецОбъекта() и ЗаписатьНачалоМассива()/ЗаписатьКонецМассива().ПараметрыЗаписиJSON очень полезен для форматирования выходного файла JSON. Используйте его, чтобы сделать JSON-файл более читаемым, добавляя отступы (например, Символы.Таб) и переносы строк. Это значительно упрощает отладку.JSONСтрока(): Если вам нужен JSON не в файле, а в виде строки (например, для передачи по HTTP), вы можете использовать глобальную функцию JSONСтрока(Значение, ПараметрыЗаписиJSON). Она принимает Структуру или Массив и возвращает их JSON-представление в виде строки.Следуя этим рекомендациям и используя один из предложенных способов, вы сможете надежно формировать JSON-файлы любой сложности.