Как правильно выгрузить данные в формате JSON из 1С и избежать ошибки «Неверный порядок записи JSON»

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

Выгрузка данных во внешние системы в формате JSON — одна из самых частых задач в практике программиста 1С. На первый взгляд она кажется простой, однако при использовании низкоуровневых методов записи или некорректном проектировании циклов разработчики часто сталкиваются с системными ошибками. Самая распространенная из них — «Неверный порядок записи JSON».

В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем структуру исходных данных, исправим логические ошибки в запросе 1С и рассмотрим два надежных способа выгрузки: стандартный JSON-массив и построчный формат NDJSON (JSON Lines).

Выясняем причину ошибки «Неверный порядок записи JSON»

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


ЗаписьJSON.ЗаписатьНачалоОбъекта();        
        
Для Каждого СтрокаТЧ Из РезультатЗапроса Цикл
    ЗаписьJSON.ЗаписатьИмяСвойства("title");
    ЗаписьJSON.ЗаписатьЗначение(Строка(СтрокаТЧ.Номенклатура), Ложь);
    // ... запись остальных свойств ...
    ЗаписьJSON.ЗаписатьКонецОбъекта();    
КонецЦикла;

Разберем по шагам, что происходит во время выполнения этого цикла:

  1. Перед началом цикла вызывается метод ЗаписатьНачалоОбъекта(). Объект ЗаписьJSON переходит в состояние ожидания свойств корневого объекта.
  2. На первой итерации цикла успешно записываются свойства (title, descriptions, unit и т.д.).
  3. В конце первой итерации вызывается ЗаписатьКонецОбъекта(). Объект JSON успешно закрыт. С этого момента запись новых свойств невозможна, так как документ логически завершен.
  4. На второй итерации цикла код пытается выполнить команду ЗаписатьИмяСвойства("title"). Но поскольку корневой объект уже был закрыт на предыдущем шаге, платформа генерирует исключение «Неверный порядок записи JSON». Объект ЗаписьJSON строго следит за соответствием стандарту и не позволяет писать свойства «в пустоту».

В первом варианте кода автора была другая ошибка — метод ОткрытьФайл вызывался внутри цикла. Это приводило к тому, что при обработке каждой новой строки файл на диске полностью перезаписывался, и в итоге в него попадала только последняя запись из выборки.

Анализируем формат данных: классический JSON против NDJSON

Посмотрим на шаблон, предоставленный автором темы:


{
    "title": "Домашний хлеб",
    "descriptions": "",
    "unit": "кг",
    "price": 100,
    "amount": 50,
    "categories": "Мучное изделие"
}
{
    "title": "Хлеб ржаной",
    "descriptions": "",
    "unit": "шт",
    "price": 120,
    "amount": 70,
    "categories": "Мучное изделие"
}

Важно понимать, что представленный шаблон не является классическим единым документом JSON. В классическом JSON может быть только один корневой элемент — либо один объект { ... }, либо один массив [ ... ]. Шаблон автора представляет собой набор независимых JSON-объектов, идущих друг за другом без разделяющих запятых.

Этот формат называется NDJSON (Newline Delimited JSON) или JSON Lines (JSONL). Он широко применяется при обмене большими объемами данных, так как позволяет читать и обрабатывать файл построчно, не загружая весь документ целиком в оперативную память. Способы формирования классического JSON и NDJSON в 1С существенно различаются. Ниже мы детально разберем оба варианта.

Исправляем и оптимизируем запрос 1С

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


|ВЫБРАТЬ
|    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
|    ТоварыНаСкладахОстатки.Склад,
|    ТоварыНаСкладахОстатки.КоличествоОстаток,
|...
|СГРУППИРОВАТЬ ПО
|    ТоварыНаСкладахОстатки.Номенклатура,
|    ТоварыНаСкладахОстатки.Склад,
|    ТоварыНаСкладахОстатки.КоличествоОстаток,

Проанализируем эту конструкцию. Во-первых, поле Склад выбирается в запросе, но никак не используется при формировании JSON. Во-вторых, группировка производится по полю КоличествоОстаток без использования агрегирующих функций (таких как СУММА).

Если один и тот же товар хранится на нескольких складах (например, на «Складе 1» лежит 10 шт, а на «Складе 2» — 20 шт), запрос вернет две отдельные строки для одной номенклатуры. В результирующий файл запишутся два дублирующих JSON-объекта с разным количеством, что исказит логику выгрузки.

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    |    СУММА(ТоварыНаСкладахОстатки.КоличествоОстаток) КАК КоличествоОстаток,
    |    ТоварыНаСкладахОстатки.Номенклатура.Родитель КАК Родитель,
    |    ЦеныНоменклатурыМагазиновСрезПоследних.Цена КАК Цена,
    |    ТоварыНаСкладахОстатки.Номенклатура.БазоваяЕдиницаИзмерения КАК БазоваяЕдиницаИзмерения
    |ИЗ
    |    РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
    |    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатурыМагазинов.СрезПоследних КАК ЦеныНоменклатурыМагазиновСрезПоследних
    |        ПО ТоварыНаСкладахОстатки.Номенклатура = ЦеныНоменклатурыМагазиновСрезПоследних.Номенклатура
    |ГДЕ
    |    ТоварыНаСкладахОстатки.КоличествоОстаток > 0
    |    И ТоварыНаСкладахОстатки.Номенклатура.ПометкаУдаления = ЛОЖЬ
    |    И ТоварыНаСкладахОстатки.Номенклатура.ЭтоГруппа = ЛОЖЬ
    |
    |СГРУППИРОВАТЬ ПО
    |    ТоварыНаСкладахОстатки.Номенклатура,
    |    ТоварыНаСкладахОстатки.Номенклатура.Родитель,
    |    ЦеныНоменклатурыМагазиновСрезПоследних.Цена,
    |    ТоварыНаСкладахОстатки.Номенклатура.БазоваяЕдиницаИзмерения
    |
    |УПОРЯДОЧИТЬ ПО
    |    Номенклатура";

РезультатЗапроса = Запрос.Выполнить().Выгрузить();

Решение №1: Выгрузка в стандартный JSON-массив (Рекомендуемый способ)

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

Объектный подход намного надежнее: он автоматически заботится о корректном экранировании спецсимволов в строках, преобразовании типов данных (числа, булевы значения, даты) и гарантирует отсутствие синтаксических ошибок в готовом файле. Мы соберем данные в массив структур 1С, а затем запишем его одной командой ЗаписатьJSON().

Рассмотрим этот алгоритм на практике:


// Создаем массив для накопления объектов нашей номенклатуры
МассивДанных = Новый Массив;

Для Каждого СтрокаТЧ Из РезультатЗапроса Цикл
    // Для каждого товара формируем структуру с нужными ключами
    ТоварОбъект = Новый Структура;
    ТоварОбъект.Вставить("title", Строка(СтрокаТЧ.Номенклатура));
    ТоварОбъект.Вставить("descriptions", "");
    ТоварОбъект.Вставить("unit", Строка(СтрокаТЧ.БазоваяЕдиницаИзмерения));
    ТоварОбъект.Вставить("price", Число(СтрокаТЧ.Цена));
    
    // Округляем остаток до целого числа, как требовалось в исходном коде
    ТоварОбъект.Вставить("amount", Цел(Число(СтрокаТЧ.КоличествоОстаток)));
    
    // В качестве категории записываем наименование родительской группы
    ТоварОбъект.Вставить("categories", Строка(СтрокаТЧ.Родитель));
    
    // Добавляем заполненную структуру в общий массив
    МассивДанных.Добавить(ТоварОбъект);
КонецЦикла;

// Инициализируем объект ЗаписьJSON
ЗаписьJSON = Новый ЗаписьJSON;

// Настраиваем параметры записи: укажем кодировку UTF-8 и использование табуляции для читаемости
ПараметрыЗаписи = Новый ПараметрыЗаписиJSON(, Символы.Таб);

Попытка
    ЗаписьJSON.ОткрытьФайл("D:\json_array.json", , , ПараметрыЗаписи);
    
    // Сериализуем массив структур в JSON одной операцией
    ЗаписатьJSON(ЗаписьJSON, МассивДанных);
    
    ЗаписьJSON.Закрыть();
    Сообщить("Выгрузка в классический JSON успешно завершена!");
Исключение
    Сообщить("Произошла ошибка при записи файла: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;

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


[
	{
		"title": "Домашний хлеб",
		"descriptions": "",
		"unit": "кг",
		"price": 100,
		"amount": 50,
		"categories": "Мучное изделие"
	},
	{
		"title": "Хлеб ржаной",
		"descriptions": "",
		"unit": "шт",
		"price": 120,
		"amount": 70,
		"categories": "Мучное изделие"
	}
]

Решение №2: Выгрузка строго по шаблону автора (формат NDJSON / JSON Lines)

Если принимающая внешняя система настроена строго на построчный формат (NDJSON) и не умеет обрабатывать стандартный массив, нам необходимо сформировать файл точно по шаблону автора — где каждый объект записан отдельно, без запятых между ними.

Напрямую использовать объект ЗаписьJSON с записью в файл здесь не получится, так как при попытке начать второй объект без открытия массива он выбросит ошибку структуры. Чтобы обойти это ограничение, мы будем использовать комбинированный подход:

  1. Для каждого товара формируем отдельную структуру 1С.
  2. Сериализуем эту структуру в строку JSON в оперативной памяти с помощью метода УстановитьСтроку() объекта ЗаписьJSON.
  3. Полученные JSON-строки записываем по очереди в файл с помощью объекта ЗаписьТекста, разделяя их стандартным символом переноса строки.

Проанализируем код реализации этого метода:


// Открываем текстовый файл на запись в кодировке UTF-8 без BOM
ТекстВыгрузки = Новый ЗаписьТекста("D:\json_lines.json", КодировкаТекста.UTF8);

Для Каждого СтрокаТЧ Из РезультатЗапроса Цикл
    // Подготавливаем структуру данных для текущей строки номенклатуры
    ТоварОбъект = Новый Структура;
    ТоварОбъект.Вставить("title", Строка(СтрокаТЧ.Номенклатура));
    ТоварОбъект.Вставить("descriptions", "");
    ТоварОбъект.Вставить("unit", Строка(СтрокаТЧ.БазоваяЕдиницаИзмерения));
    ТоварОбъект.Вставить("price", Число(СтрокаТЧ.Цена));
    ТоварОбъект.Вставить("amount", Цел(Число(СтрокаТЧ.КоличествоОстаток)));
    ТоварОбъект.Вставить("categories", Строка(СтрокаТЧ.Родитель));
    
    // Сериализуем единичный объект в строку в памяти
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку(Новый ПараметрыЗаписиJSON(, Символы.Таб));
    ЗаписатьJSON(ЗаписьJSON, ТоварОбъект);
    СтрокаJSON = ЗаписьJSON.Закрыть();
    
    // Записываем полученный JSON-блок в текстовый файл как отдельную строку
    ТекстВыгрузки.ЗаписатьСтроку(СтрокаJSON);
КонецЦикла;

// Закрываем текстовый файл для сохранения данных на диск
ТекстВыгрузки.Закрыть();
Сообщить("Выгрузка в формате JSON Lines успешно завершена!");

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

Резюме и лучшие практики

Подводя итоги, сформулируем основные правила, которые помогут избежать ошибок при работе с JSON в среде 1С:

← На главную