Как сравнить два документа с разной структурой табличных частей в двух базах 1С?

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

При интеграции систем, миграции данных или сверке распределенных баз данных программисты 1С часто сталкиваются с необходимостью сравнения объектов. Ситуация значительно усложняется, если конфигурации двух баз имеют различия в структуре метаданных. Например, в Базе №1 документ содержит табличные части Состав1 и Состав2, а в Базе №2 этот же документ содержит только табличную часть Состав1. Наша задача — сравнить шапки документов и общую для обеих баз табличную часть Состав1, полностью проигнорировав изменения в Состав2.

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

Скрытые ловушки стандартной сериализации

На первый взгляд идея кажется простой: сериализовать оба документа в JSON, получить хэш-строку (например, MD5) и сравнить их. Однако при детальном рассмотрении этот подход наталкивается на три серьезные проблемы:

  1. Проблема пустых табличных частей при сравнении хэшей. Если мы попытаемся использовать метод Очистить() для удаления содержимого Состав2 перед сериализацией в Базе №1, табличная часть просто станет пустой. Сам узел ТЧ не исчезнет из метаданных. При сериализации через СериализаторXDTO в JSON все равно запишется пустое свойство: "Состав2": []. В Базе №2 этого свойства не будет вообще. Как результат — хэши файлов гарантированно разойдутся, даже если все остальные данные идентичны.
  2. Различие UUID для ссылочных типов. Если документы содержат ссылки на справочники (например, контрагенты, номенклатура, склады), стандартная сериализация запишет их уникальные идентификаторы (UUID). Если базы не синхронизированы по UUID (даже если коды и наименования элементов совпадают), хэш-строки документов будут отличаться. Для корректного сравнения нам потребуется приводить ссылки к строковому представлению (коду или артикулу).
  3. Ограничения типа Структура при чтении JSON. При выгрузке объектов через СериализаторXDTO в JSON-строку записываются служебные символы, например, "#type" для обозначения типа данных. Символ решетки # является недопустимым в именах ключей для объекта Структура в 1С. Попытка десериализовать такой JSON напрямую в структуру вызовет критическую ошибку. Чтение необходимо выполнять строго в объект типа Соответствие.

Решение 1. Динамическая сборка структуры сравнения (Рекомендуемый способ)

Самый надежный и контролируемый способ — это ручная сборка "чистой" структуры данных на основе метаданных документа. Мы можем написать универсальную функцию, которая обойдет все реквизиты шапки и нужные табличные части, пропустив исключенные объекты. Этот метод также решает проблему с UUID, позволяя автоматически заменять ссылки их кодами.

Рассмотрим подробный пример реализации такой функции:


// Функция собирает структуру только из нужных реквизитов и табличных частей
Функция ПолучитьДанныеДляСравнения(ДокументСсылка, ИсключаемыеРеквизиты = Неопределено, ИсключаемыеТЧ = Неопределено) Экспорт
    Если ИсключаемыеРеквизиты = Неопределено Тогда
        ИсключаемыеРеквизиты = Новый Массив;
    КонецЕсли;
    Если ИсключаемыеТЧ = Неопределено Тогда
        ИсключаемыеТЧ = Новый Массив;
    КонецЕсли;
    
    Объект = ДокументСсылка.ПолучитьОбъект();
    МетаданныеОбъекта = Объект.Метаданные();
    Результат = Новый Структура;
    
    // 1. Обработка реквизитов шапки документа
    Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
        ИмяРеквизита = Реквизит.Имя;
        Если ИсключаемыеРеквизиты.Найти(ИмяРеквизита) <> Неопределено Тогда
            Продолжить;
        КонецЕсли;
        
        Значение = Объект[ИмяРеквизита];
        
        // Заменяем ссылки на коды для избежания конфликтов UUID в разных базах
        Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(Значение)) И Значение <> Неопределено Тогда
            ЗначениеСравнения = Значение.Код; 
        ИначеЕсли Документы.ТипВсеСсылки().СодержитТип(ТипЗнч(Значение)) И Значение <> Неопределено Тогда
            ЗначениеСравнения = Значение.Номер; // Для документов используем номер
        Иначе
            ЗначениеСравнения = Значение;
        КонецЕсли;
        
        Результат.Вставить(ИмяРеквизита, ЗначениеСравнения);
    КонецЦикла;
    
    // 2. Обработка табличных частей
    Для Каждого ТЧ Из МетаданныеОбъекта.ТабличныеЧасти Цикл
        ИмяТЧ = ТЧ.Имя;
        Если ИсключаемыеТЧ.Найти(ИмяТЧ) <> Неопределено Тогда
            Продолжить;
        КонецЕсли;
        
        МассивСтрок = Новый Массив;
        Для Каждого СтрокаТЧ Из Объект[ИмяТЧ] Цикл
            СтруктураСтроки = Новый Структура;
            Для Каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
                ИмяРеквизитаТЧ = РеквизитТЧ.Имя;
                ЗначениеТЧ = СтрокаТЧ[ИмяРеквизитаТЧ];
                
                // Заменяем ссылки в табличной части
                Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(ЗначениеТЧ)) И ЗначениеТЧ <> Неопределено Тогда
                    ЗначениеСравнения = ЗначениеТЧ.Код; 
                Иначе
                    ЗначениеСравнения = ЗначениеТЧ;
                КонецЕсли;
                
                СтруктураСтроки.Вставить(ИмяРеквизитаТЧ, ЗначениеСравнения);
                
            КонецЦикла;
            МассивСтрок.Добавить(СтруктураСтроки);
        КонецЦикла;
        
        Результат.Вставить(ИмяТЧ, МассивСтрок);
    КонецЦикла;
    
    Возврат Результат;
КонецФункции

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


// Подготовка параметров исключения
ИсключаемыеТЧ = Новый Массив;
ИсключаемыеТЧ.Добавить("Состав2"); // Данную ТЧ мы игнорируем

// Получаем "чистые" данные документа
ДанныеДокумента = ПолучитьДанныеДляСравнения(Выборка.Ссылка, , ИсключаемыеТЧ);

// Сериализуем структуру в JSON-строку
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ДанныеДокумента);
СтрокаСравнения = ЗаписьJSON.Закрыть();

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

Решение 2. Сериализация в Соответствие и программное удаление ключей

Если вы хотите избежать ручного перебора метаданных и предпочитаете использовать стандартный механизм СериализаторXDTO, то вам потребуется промежуточная десериализация. Смысл метода заключается в том, чтобы выгрузить документ целиком, прочитать его в объект типа Соответствие, удалить ненужные ключи на программном уровне и сериализовать обратно.

Посмотрим на пример реализации данного алгоритма:


// Шаг 1. Сериализуем объект целиком через XDTO-сериализатор
ОбъектДокумента = Выборка.Ссылка.ПолучитьОбъект();
ЗаписьJson = Новый ЗаписьJSON;
ЗаписьJson.УстановитьСтроку();
СериализаторXDTO.ЗаписатьJSON(ЗаписьJson, ОбъектДокумента, НазначениеТипаXML.Явное);
ИсходныйJSON = ЗаписьJson.Закрыть();

// Шаг 2. Десериализуем JSON-строку в Соответствие (параметр Истина обязателен!)
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ИсходныйJSON);
ДанныеОбъекта = ПрочитатьJSON(ЧтениеJSON, Истина); 
ЧтениеJSON.Закрыть();

// Шаг 3. Удаляем ненужные узлы
// Удаляем табличную часть, которой нет во второй базе
ДанныеОбъекта.Удалить("Состав2"); 

// Также рекомендуется удалить системные свойства, которые всегда различаются между базами
ДанныеОбъекта.Удалить("ВерсияДанных"); 
ДанныеОбъекта.Удалить("Ссылка");

// Шаг 4. Сериализуем очищенные данные обратно в строку
ЗаписьJsonОчищенный = Новый ЗаписьJSON;
ЗаписьJsonОчищенный.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJsonОчищенный, ДанныеОбъекта);
ИтоговыйJSON = ЗаписьJsonОчищенный.Закрыть();

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

Использование современных методов платформы (версии 8.3.23 и выше)

Если вы работаете на современных версиях платформы 1С:Предприятие (начиная с релиза 8.3.23), то рутинные операции с созданием объектов ЗаписьJSON и ЧтениеJSON можно существенно упростить. Разработчикам стали доступны глобальные методы быстрого преобразования данных:

Использование данных методов позволяет сократить объем кода и сделать его более читаемым:


// Пример для версии 1С 8.3.23+
ДанныеДокумента = ПолучитьДанныеДляСравнения(Выборка.Ссылка, , ИсключаемыеТЧ);
СтрокаJSON = ЗаписатьЗначениеJSON(ДанныеДокумента);

Выбор конкретного метода зависит от технических требований проекта. Если в базах гарантирована идентичность UUID всех справочников (например, базы синхронизируются через РИБ), то отлично подойдет Решение 2. Если же базы независимы и синхронизация происходит по кодам/наименованиям, то единственным верным решением будет использование Решения 1 с динамической сборкой структуры.

← На главную