При интеграции устаревших систем на платформе 1С:Предприятие 7.7 с современными веб-сервисами разработчики неизбежно сталкиваются с необходимостью разбора (парсинга) входящих данных в формате JSON. Поскольку встроенных средств для работы с этим форматом в 1С 7.7 нет, стандартным подходом стало использование системного компонента MSScriptControl.ScriptControl с подключением движка JScript. Однако при попытке прочитать массив из полученной структуры данных разработчик получает ошибку. Вызов вида Инф.cars[0] завершается аварийно. Для отправки запросов к веб-сервисам и REST API можно использовать внешнюю компоненту HTTP(S)-клиента для 1С 7.7.
В этой статье мы подробно разберем, почему возникает эта ошибка, как обойти ограничения OLE-взаимодействия и как написать полноценный, быстрый и отказоустойчивый парсер JSON для вашей конфигурации 1С 7.7.
Рассмотрим природу проблемы подробнее. Когда мы передаем JSON-строку в метод Eval или Run компонента ScriptControl, JScript парсит ее и возвращает в 1С ссылку на объект в своей внутренней памяти. В отладчике 1С этот объект отображается как тип OLE.JScriptTypeInfo. С простыми свойствами (строки, числа, булевы значения) платформа 1С 7.7 работает напрямую без проблем: выражение Инф.name вернет значение свойства "name".
Однако синтаксис языка 1С 7.7 принципиально не поддерживает работу с квадратными скобками [] для обращения к элементам коллекций внешних COM/OLE-объектов. Попытка написать Инф.cars[0] вызывает синтаксическую ошибку на стороне 1С, так как платформа пытается интерпретировать квадратные скобки по своим внутренним правилам (например, как обращение к реквизиту или элементу диалога), а не передает этот вызов OLE-серверу.
Чтобы обойти данное ограничение платформы, нам необходимо реализовать вспомогательные функции на стороне самого JScript. Поскольку 1С умеет передавать OLE-объекты обратно в методы ScriptControl как параметры, мы можем написать простые функции-обертки («мосты»), выполняющие нужные операции внутри среды JScript и возвращающие результат в 1С.
Давайте посмотрим на пример реализации такого скрипта-инициализатора. Мы добавим в наш экземпляр ScriptControl функции для определения типа данных, а также для получения элементов массивов и свойств ассоциативных объектов.
Процедура ИнициализироватьСкрипт(scriptCtrl)
scriptCtrl.Language = "jscript";
// Внедряем вспомогательные функции JScript для безопасной работы из OLE
scriptCtrl.AddCode(
"function parseJSON(strJSON) {
| return JSON.parse(strJSON);
|}
|function eType(obj) {
| if (obj === null) return 'null';
| if (obj === undefined) return 'undefined';
| if (Object.prototype.toString.call(obj) === '[object Array]') return 'array';
| return typeof obj;
|}
|function aGet(arr, index) {
| return arr[index];
|}
|function oSize(obj) {
| var size = 0, key;
| for (key in obj) {
| if (obj.hasOwnProperty(key)) size++;
| }
| return size;
|}
|function oKey(obj, index) {
| var size = 0, key;
| for (key in obj) {
| if (obj.hasOwnProperty(key)) {
| if (size == index) return key;
| size++;
| }
| }
| return '';
|}
|function oValue(obj, key) {
| return obj[key];
|}"
);
КонецПроцедуры
Разберем назначение этих функций подробнее:
parseJSON(strJSON): использует встроенный и быстрый метод JSON.parse для преобразования строки в объект JScript.eType(obj): определяет тип переданного OLE-объекта (массив, объект, строка, число и т.д.), что критически важно для рекурсивного обхода дерева данных.aGet(arr, index): ключевая функция решения! Она принимает массив JScript и индекс, возвращая элемент массива обратно в 1С.oSize(obj), oKey(obj, index), oValue(obj, key): набор методов для обхода свойств объектов (ассоциативных массивов), позволяющий перебирать ключи и значения по индексу, подобно работе с объектом "Соответствие" в 1С 8.Теперь, когда у нас есть надежный мост для работы со структурами JScript, мы можем написать рекурсивную функцию на стороне 1С 7.7. Эта функция будет полностью обходить структуру JSON-объекта и преобразовывать ее в стандартные объекты 1С — СписокЗначений. Это позволит нам в дальнейшем легко обращаться к любым элементам данных с использованием привычного синтаксиса.
Проанализируем пример кода рекурсивного парсера:
Функция КонвертироватьВСписок(scriptCtrl, JSОбъект)
Перем ТипОбъекта, РезСписок, Инд, Ключ, Значение, ЭлемТип, РазмерМассива, РазмерОбъекта;
ТипОбъекта = scriptCtrl.Run("eType", JSОбъект);
Если (ТипОбъекта = "string") или (ТипОбъекта = "number") или (ТипОбъекта = "boolean") Тогда
Возврат JSОбъект;
ИначеЕсли ТипОбъекта = "array" Тогда
РезСписок = СоздатьОбъект("СписокЗначений");
РазмерМассива = JSОбъект.length;
Для Инд = 0 По РазмерМассива - 1 Цикл
Значение = scriptCtrl.Run("aGet", JSОбъект, Инд);
ЭлемТип = scriptCtrl.Run("eType", Значение);
Если (ЭлемТип = "object") или (ЭлемТип = "array") Тогда
РезСписок.ДобавитьЗначение(КонвертироватьВСписок(scriptCtrl, Значение));
Иначе
РезСписок.ДобавитьЗначение(Значение);
КонецЕсли;
КонецЦикла;
Возврат РезСписок;
ИначеЕсли ТипОбъекта = "object" Тогда
РезСписок = СоздатьОбъект("СписокЗначений");
РазмерОбъекта = scriptCtrl.Run("oSize", JSОбъект);
Для Инд = 0 По РазмерОбъекта - 1 Цикл
Ключ = scriptCtrl.Run("oKey", JSОбъект, Инд);
Значение = scriptCtrl.Run("oValue", JSОбъект, Ключ);
ЭлемТип = scriptCtrl.Run("eType", Значение);
Если (ЭлемТип = "object") или (ЭлемТип = "array") Тогда
РезСписок.Установить(Ключ, КонвертироватьВСписок(scriptCtrl, Значение));
Иначе
РезСписок.Установить(Ключ, Значение);
КонецЕсли;
КонецЦикла;
Возврат РезСписок;
Иначе
Возврат ""; // Для null, undefined и неопределенных типов возвращаем пустую строку
КонецЕсли;
КонецФункции
Теперь посмотрим, как просто и красиво выглядит основной исполняемый код разбора JSON в 1С с использованием нашей функции:
Процедура ВыполнитьРазборJSON()
Перем scriptCtrl, ИсходнаяСтрока, JSONОбъект, Данные1С, МассивМашин, ПерваяМашина, ВтораяМашина, Имя, Возраст;
scriptCtrl = СоздатьОбъект("MSScriptControl.ScriptControl");
ИнициализироватьСкрипт(scriptCtrl);
// Исходные JSON-данные с массивом
ИсходнаяСтрока = "{""name"":""John"",""age"":30,""cars"":[""Ford"",""BMW"",""Fiat""]}";
// Парсим строку в объект JScript
JSONОбъект = scriptCtrl.Run("parseJSON", ИсходнаяСтрока);
// Конвертируем дерево JScript в иерархический СписокЗначений 1С
Данные1С = КонвертироватьВСписок(scriptCtrl, JSONОбъект);
// Получаем простые значения
Имя = Данные1С.Получить("name"); // "John"
Возраст = Данные1С.Получить("age"); // 30
// Работаем с массивом напрямую через СписокЗначений!
МассивМашин = Данные1С.Получить("cars");
// Вытаскиваем элементы массива по индексу
ПерваяМашина = МассивМашин.ПолучитьЗначение(1); // В 1С 7.7 индексация СпискаЗначений начинается с 1!
ВтораяМашина = МассивМашин.ПолучитьЗначение(2);
Сообщить("Имя: " + Имя + ", Возраст: " + Возраст);
Сообщить("Первая машина в списке: " + ПерваяМашина); // Выведет "Ford"
Сообщить("Вторая машина в списке: " + ВтораяМашина); // Выведет "BMW"
КонецПроцедуры
Компонент MSScriptControl.ScriptControl является устаревшим 32-разрядным ActiveX-элементом. В современных версиях Windows (начиная с Windows 10/11 и Windows Server 2016/2019) он по умолчанию отключен из соображений безопасности или вовсе отсутствует в системе, что может вызывать ошибки при запуске вашей обработки 1С 7.7 у конечных пользователей.
Более надежной, современной и стабильной альтернативой является использование стандартного объекта htmlfile, который присутствует в любой версии Windows «из коробки» и работает без необходимости регистрации дополнительных библиотек в реестре. Рассмотрим пример инициализации парсера через htmlfile:
Функция ПолучитьПарсерHTML()
Перем HTML, scriptCtrl;
HTML = СоздатьОбъект("htmlfile");
// Принудительно включаем режим совместимости с Internet Explorer 9 и выше для поддержки JSON.parse
HTML.write("<meta http-equiv='x-ua-compatible' content='IE=9' />");
// Получаем доступ к глобальному объекту window, выполняющему роль ScriptControl
scriptCtrl = HTML.parentWindow;
// Добавляем наши функции-мосты в глобальную область видимости документа
scriptCtrl.execScript(
"function eType(obj) {
| if (obj === null) return 'null';
| if (obj === undefined) return 'undefined';
| if (Object.prototype.toString.call(obj) === '[object Array]') return 'array';
| return typeof obj;
|}
|function aGet(arr, index) { return arr[index]; }
|function oSize(obj) {
| var size = 0, key;
| for (key in obj) { if (obj.hasOwnProperty(key)) size++; }
| return size;
|}
|function oKey(obj, index) {
| var size = 0, key;
| for (key in obj) { if (obj.hasOwnProperty(key)) { if (size == index) return key; size++; } }
| return '';
|}
|function oValue(obj, key) { return obj[key]; }"
);
Возврат scriptCtrl;
КонецФункции
При использовании htmlfile вам больше не нужен отдельный метод parseJSON в коде скрипта, так как объект JSON поддерживается движком по умолчанию. Вы можете вызывать парсинг прямо из 1С следующим образом:
Парсер = ПолучитьПарсерHTML();
JSONОбъект = Парсер.JSON.parse(ИсходнаяСтрока);
Данные1С = КонвертироватьВСписок(Парсер, JSONОбъект);
Разберем основные практические трудности и подводные камни, с которыми вы можете столкнуться при реализации обмена данными по протоколу JSON:
ADODB.Stream перед вызовом парсера.JSON.parse требует наличия в системе Internet Explorer версии 8 или выше. Если база запускается на старой ОС (например, Windows Server 2003 без обновлений), то JSON.parse вызовет ошибку. Для обеспечения обратной совместимости в таких средах лучше использовать конструкцию (new Function('return(' + strJSON + ');'))() вместо JSON.parse.Scripting.FileSystemObject или ADODB.Stream, чтобы минимизировать нагрузку на память платформы 1С.Использование описанных выше подходов и функций-мостов позволит вам создавать надежные, современные и высокопроизводительные интеграционные механизмы в 1С 7.7, полностью закрывая вопросы разбора сложных вложенных структур и массивов JSON. При необходимости можно воспользоваться библиотекой процедур и функций для 1С 7.7 с готовыми модулями обработки JSON и HTTP.