В практике разработки на платформе 1С:Предприятие часто возникает задача быстрого сравнения двух коллекций данных, например, таблиц значений. Одним из соблазнительных и, на первый взгляд, простых способов является получение их строкового представления с помощью встроенной функции ЗначениеВСтрокуВнутр и последующее сопоставление этих строк. Однако этот метод таит в себе скрытые подводные камни, которые могут привести к ложным результатам сравнения визуально абсолютно идентичных таблиц.
В этой статье мы подробно разберем, почему две таблицы значений с одинаковыми строками, колонками, типами данных и порядком следования элементов могут давать разные результаты при сериализации через ЗначениеВСтрокуВнутр. Мы выясним истинную причину этого поведения и рассмотрим надежные альтернативные варианты сравнения таблиц.
Представим типичную ситуацию. У нас есть две таблицы значений. Обе содержат две колонки: первая имеет тип ссылки на перечисление, вторая — булево значение. Структура колонок, типы данных, отсутствие индексов и порядок строк в обеих таблицах полностью совпадают.
Однако при вызове функции ЗначениеВСтрокуВнутр для обеих таблиц мы получаем почти идентичные строки, за исключением одной небольшой детали в системном блоке в конце строки. В одной таблице мы видим фрагмент },-1,2}, а в другой — },1,2},.
Давайте проанализируем фрагменты строк сериализации, полученные в ходе исследования:
Для первой таблицы:
{"#",acf6192e-81ca-46ef-93a6-5a6968b78663,
{9,
{2,
{0,"ОтборУслуги",
{"Pattern",
{"#",746021e1-31bf-435f-aa88-25683c07738f}
},"",20},
{1,"Включен",
{"Pattern",
{"B"}
},"",20}
},
{2,2,0,0,1,1,
{1,3,
{2,0,2,
{"#",746021e1-31bf-435f-aa88-25683c07738f,22593:84fcea3d17f963604fc0e4fbdce9de66},
{"B",1},0},
{2,1,2,
{"#",746021e1-31bf-435f-aa88-25683c07738f,22593:a8c5e9035e6b05a846841f3e7a0b70fe},
{"B",1},0},
{2,2,2,
{"#",746021e1-31bf-435f-aa88-25683c07738f,22593:af0b644a0189a4794259a8985f707275},
{"B",0},0}
},-1,2},
{0,0}
}
}
Для второй таблицы:
{"#",acf6192e-81ca-46ef-93a6-5a6968b78663,
{9,
{2,
{0,"ОтборУслуги",
{"Pattern",
{"#",746021e1-31bf-435f-aa88-25683c07738f}
},"",20},
{1,"Включен",
{"Pattern",
{"B"}
},"",20}
},
{2,2,0,0,1,1,
{1,3,
{2,0,2,
{"#",746021e1-31bf-435f-aa88-25683c07738f,22593:84fcea3d17f963604fc0e4fbdce9de66},
{"B",1},0},
{2,1,2,
{"#",746021e1-31bf-435f-aa88-25683c07738f,22593:a8c5e9035e6b05a846841f3e7a0b70fe},
{"B",1},0},
{2,2,2,
{"#",746021e1-31bf-435f-aa88-25683c07738f,22593:af0b644a0189a4794259a8985f707275},
{"B",0},0}
},1,2},
{0,0}
}
}
На первый взгляд кажется, что таблицы абсолютно идентичны, но строковое сравнение выдаст Ложь из-за различия в параметрах в самом конце массива строк. Давайте разберемся, откуда берется эта разница.
Среди разработчиков существует ошибочное мнение, что эти цифры в конце структуры данных отвечают за количество строк или колонок. Однако при детальном анализе внутреннего формата (уникальный идентификатор типа acf6192e-81ca-46ef-93a6-5a6968b78663 соответствует объекту ТаблицаЗначений) становится ясно, что количество колонок и строк описывается гораздо раньше.
Например, число 3 в блоке {1,3, ...} четко указывает на наличие 3 строк в обеих таблицах. А истинная причина скрывается в сохранении состояния интерфейсного фокуса (активного элемента).
Платформа 1С при сериализации сложных коллекций, особенно связанных с экранными формами, сохраняет не только сухие данные, но и контекст отображения. Два параметра в конце блока данных строк }, <ИдентификаторАктивнойКолонки>, <ИдентификаторАктивнойСтроки>} описывают положение фокуса ввода:
-1 и 1) — это внутренний индекс активной (выделенной) колонки:
-1 указывает на то, что в таблице нет выделенной активной колонки. Это стандартное состояние для таблиц значений, созданных и заполненных исключительно программно в памяти (в коде).1 означает, что активной на момент сериализации была колонка с индексом 1 (в нашем примере это вторая колонка таблицы — Включен, так как индексация колонок начинается с 0).2 в обоих случаях) — это внутренний идентификатор активной строки:
2 (что указывает на третью строку). Для программно созданной таблицы это объясняется тем, что при последовательном добавлении строк через метод Добавить() внутренний индекс активной строки автоматически смещается на последнюю добавленную строку (индекс 2 для третьей строки).Разница в поведении объясняется тем, как именно были получены эти таблицы значений:
Выгрузить(), примененный к табличной части или таблице на форме, копирует данные вместе с их интерактивным контекстом (какая ячейка была активна, на какой колонке стоял фокус пользователя). Отсюда мы получаем значение активной колонки 1.СкопироватьКолонки() создает абсолютно чистую таблицу в оперативной памяти, лишенную какого-либо интерфейсного контекста. При последующем заполнении этой таблицы строками программным путем активная колонка остается неопределенной (значение -1).Именно поэтому прямое сравнение через ЗначениеВСтрокуВнутр() является крайне хрупким и ненадежным механизмом. Достаточно пользователю просто кликнуть по другой колонке на форме перед выполнением операции — и ваш алгоритм сравнения вернет Ложь, даже если данные внутри таблиц не изменились ни на один символ.
Ниже мы рассмотрим пять надежных способов корректного сравнения таблиц значений, которые избавят вас от подобных проблем.
Использование сериализации в XML через СериализаторXDTO позволяет выгрузить только структуру и данные таблицы значений, полностью очищенные от системного мусора, метаданных отображения и фокусов интерфейса. Две таблицы с одинаковыми данными всегда дадут абсолютно идентичные XML-строки.
Давайте реализуем универсальную функцию для получения «чистой» строки таблицы значений:
Функция ПолучитьЧистуюСтрокуXML(ТаблицаЗначений) Экспорт
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
// Сериализуем объект в XML, используя явное назначение типов
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, ТаблицаЗначений, НазначениеТипаXML.Явное);
Возврат ЗаписьXML.Закрыть();
КонецФункции
Теперь для сравнения двух таблиц значений достаточно вызвать эту функцию для каждой из них и сравнить полученные строки:
Если ПолучитьЧистуюСтрокуXML(Таблица1) = ПолучитьЧистуюСтрокуXML(Таблица2) Тогда
// Таблицы гарантированно идентичны по структуре и данным
Иначе
// Есть различия
КонецЕсли;
Аналогично XML, современная платформа 1С поддерживает сериализацию в формат JSON. Этот метод работает быстро и также полностью игнорирует внутреннее состояние интерфейса платформы.
Разберем пример реализации сравнения через JSON:
Функция ПолучитьСтрокуJSON(ТаблицаЗначений) Экспорт
ЗаписьJSON = Новый ЗаписьJSON;
ПараметрыЗаписи = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет);
ЗаписьJSON.УстановитьСтроку(ПараметрыЗаписи);
ЗаписатьJSON(ЗаписьJSON, ТаблицаЗначений);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
Полученный JSON-текст содержит исключительно данные колонок и строк, что делает его идеальным кандидатом для быстрого и надежного сравнения.
Если ваша конфигурация разработана на базе БСП (что справедливо для абсолютного большинства современных решений, таких как ERP, УТ, КА, БП, ЗУП), вам не нужно писать собственный код для обхода таблиц. В БСП уже есть готовый, оптимизированный и протестированный метод для сравнения коллекций.
Для сравнения таблиц воспользуемся методом общего модуля ОбщегоНазначенияКлиентСервер.КоллекцииИдентичны(). Он выполняет глубокое сравнение элементов коллекций, проверяя типы колонок, состав данных и порядок их следования.
Пример вызова метода:
// Метод возвращает Истина, если коллекции полностью совпадают
Если ОбщегоНазначенияКлиентСервер.КоллекцииИдентичны(Таблица1, Таблица2) Тогда
// Действия, если таблицы одинаковые
КонецЕсли;
Этот способ является наиболее предпочтительным с точки зрения стандартов разработки фирмы 1С, так как он гарантирует корректную работу и поддерживаемость вашего кода при обновлениях системы.
Если таблицы гарантированно небольшого размера (например, до 100 строк) или вам важно точно знать, в какой именно строке или колонке произошло расхождение, можно реализовать классический обход коллекций в цикле.
Рассмотрим алгоритм детального сравнения:
Функция ТаблицыИдентичны(Таблица1, Таблица2) Экспорт
// 1. Проверяем количество строк
Если Таблица1.Количество() <> Таблица2.Количество() Тогда
Возврат Ложь;
КонецЕсли;
// 2. Проверяем количество и имена колонок
Если Таблица1.Колонки.Количество() <> Таблица2.Колонки.Количество() Тогда
Возврат Ложь;
КонецЕсли;
Для Каждого Колонка Из Таблица1.Колонки Цикл
Если Таблица2.Колонки.Найти(Колонка.Имя) = Неопределено Тогда
Возврат Ложь; // Колонки с таким именем нет во второй таблице
КонецЕсли;
КонецЦикла;
// 3. Построчное сравнение значений
КоличествоСтрок = Таблица1.Количество();
Для Индекс = 0 По КоличествоСтрок - 1 Цикл
Строка1 = Таблица1[Индекс];
Строка2 = Таблица2[Индекс];
Для Каждого Колонка Из Таблица1.Колонки Цикл
Если Строка1[Колонка.Имя] <> Строка2[Колонка.Имя] Тогда
Возврат Ложь; // Найдено несовпадение значений
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат Истина;
КонецФункции
Этот метод работает на чистом встроенном языке 1С, не требует выполнения ресурсоемкой сериализации, не нагружает память созданием больших строк XML/JSON и позволяет легко добавить логику логирования расхождений.
Если по каким-то причинам в вашем проекте критически важно использовать именно внутренний формат сериализации 1С (например, вы ограничены рамками легаси-архитектуры), вам необходимо принудительно очистить интерфейсный контекст таблицы перед получением строки.
Самый простой способ сделать это — создать полную копию таблицы методом Скопировать() без параметров. При копировании таблицы в памяти создается новый объект, который теряет привязку к форме, активным строкам и фокусам ввода:
// Очищаем интерактивный контекст путем создания копий в памяти
ЧистаяТаблица1 = Таблица1.Скопировать();
ЧистаяТаблица2 = Таблица2.Скопировать();
// Теперь сравнение строк пройдет корректно
Если ЗначениеВСтрокуВнутр(ЧистаяТаблица1) = ЗначениеВСтрокуВнутр(ЧистаяТаблица2) Тогда
// Таблицы идентичны
КонецЕсли;
Однако помните, что метод ЗначениеВСтрокуВнутр официально не документирован для прикладных задач сравнения, и его структура может незначительно меняться от релиза к релизу платформы 1С.
Различия во внутреннем представлении таблиц значений при использовании ЗначениеВСтрокуВнутр вызваны не количеством строк или колонок, а сохранением системного интерфейсного фокуса активного элемента формы (индексов активной строки и колонки). Использование этого метода для сравнения объектов напрямую в коде крайне нежелательно.
Для надежной работы ваших алгоритмов всегда выбирайте сериализацию в XML (XDTO), JSON или используйте стандартные функции БСП, такие как КоллекцииИдентичны. Это гарантирует стабильность вашей программы и защитит ее от неожиданных сбоев при интерактивных действиях пользователей в формах.