Как дописать данные в JSON-файл без полной перезаписи всего содержимого

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

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

Проблема структуры JSON и стандартные ограничения

JSON-файл, представляющий собой массив объектов, всегда заканчивается закрывающей квадратной скобкой ]. Чтобы добавить новый элемент в этот массив, мы не можем просто дописать строку в конец файла, так как это нарушит синтаксис (данные окажутся за пределами массива). Традиционный подход "прочитать все в память — добавить элемент — записать обратно" быстро приводит к ошибкам "Недостаточно памяти". Поэтому нам необходимо рассмотреть методы прямой манипуляции файловым указателем.

Метод 1: Манипуляция указателем через ФайловыйПоток

Для решения задачи мы воспользуемся объектом ФайловыйПоток. Суть метода заключается в том, чтобы открыть файл в режиме дозаписи, переместить указатель на один или несколько байтов назад (чтобы "стереть" закрывающую скобку) и записать новые данные, начиная с запятой.

Рассмотрим пример кода, который реализует этот подход:


&НаСервере
Процедура ДозаписатьВJSONНаСервере(ПутьКФайлу, НовыеДанныеСтруктура)
    
    // Подготовим текст для записи: запятая, сам объект и закрывающая скобка массива
    ЗаписьJSON = Новый ЗаписьJSON;
    ПараметрыЗаписиJSON = Новый ПараметрыЗаписиJSON(, Символы.Таб);
    ЗаписьJSON.УстановитьСтроку(ПараметрыЗаписиJSON);
    ЗаписатьJSON(ЗаписьJSON, НовыеДанныеСтруктура);
    
    // Убираем возможные лишние символы и подготавливаем строку вставки
    СтрокаОбъекта = ЗаписьJSON.Закрыть();
    ТекстДляВставки = "," + СтрокаОбъекта + "]";
    
    // Преобразуем строку в буфер двоичных данных (важно соблюдать кодировку UTF-8)
    Буфер = ПолучитьБуферДвоичныхДанныхИзСтроки(ТекстДляВставки, КодировкаТекста.UTF8);
    
    // Открываем файл для дозаписи
    ФП = Новый ФайловыйПоток(ПутьКФайлу, РежимОткрытияФайла.Дописать);
    
    // Сдвигаем указатель назад на 1 байт, чтобы встать на позицию символа "]"
    // ВНИМАНИЕ: это сработает, если файл не имеет переноса строки в конце!
    ФП.Перейти(-1, ПозицияВПотоке.Конец);
    
    // Записываем наш буфер
    ФП.Записать(Буфер, 0, Буфер.Размер);
    
    ФП.Закрыть();
    
КонецПроцедуры

Важные нюансы работы с байтами и кодировками

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

  1. Символы переноса строки (CRLF): Если файл JSON был сохранен с переносом строки в конце (многие редакторы добавляют \r\n автоматически), то последний байт будет не ], а 0x0A. В этом случае переход на -1 байт приведет к повреждению файла. Профессиональный подход требует проверки последних байтов файла перед записью.
  2. Кодировка и BOM: При использовании UTF-8 в начале файла могут стоять три байта EF BB BF (Byte Order Mark). Хотя при дозаписи в конец это не мешает, важно, чтобы кодировка дописываемого фрагмента (через ПолучитьБуферДвоичныхДанныхИзСтроки) строго совпадала с кодировкой файла.
  3. Пустой массив: Если файл содержит пустой массив [], логика с запятой , перед первым элементом будет ошибочной. Нужно проверять, пуст ли массив, перед вставкой.

Метод 2: Использование формата JSON Lines (NDJSON)

Если ваша задача — ведение логов или накопление данных, формат "массив объектов" может быть не лучшим выбором. Рассмотрим альтернативу — JSON Lines. В этом формате каждый объект JSON записывается на новой строке без общих квадратных скобок и запятых.

Преимущество этого метода в том, что для добавления данных нам достаточно использовать обычный объект ЗаписьТекста в режиме Дописывать = Истина.


Процедура ДобавитьЛогJSON(ПутьКФайлу, Данные)
    Запись = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8, , Истина); // Истина - режим дозаписи
    
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, Данные);
    СтрокаОбъекта = ЗаписьJSON.Закрыть();
    
    Запись.ЗаписатьСтроку(СтрокаОбъекта);
    Запись.Закрыть();
КонецПроцедуры

Для чтения такого файла в 1С мы используем построчное чтение:


Текст = Новый ЧтениеТекста(ПутьКФайлу);
Строка = Текст.ПрочитатьСтроку();
Пока Строка <> Неопределено Цикл
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.УстановитьСтроку(Строка);
    Объект = ПрочитатьJSON(ЧтениеJSON);
    // Обработка объекта...
    Строка = Текст.ПрочитатьСтроку();
КонецЦикла;

Метод 3: Оптимизация через потоковое чтение-запись (Метод слияния)

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

Разберем по шагам этот алгоритм:

  1. Открываем ЧтениеТекста для исходного файла.
  2. Открываем ЗаписьТекста для нового временного файла.
  3. Читаем данные порциями и записываем в новый файл.
  4. Когда доходим до конца, не записываем закрывающую скобку ].
  5. Дописываем , и новые объекты.
  6. Дописываем ] и закрываем файлы.
  7. Заменяем старый файл новым.

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

Выводы и рекомендации

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

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

← На главную