Работа с JSON на ISBL с помощью JavaScript (Часть 1)

25 6

Введение

JSON - один из наиболее удобных форматов данных при взаимодействии с JavaScript. Описывать его в данной статье не буду - информации много на просторах интернета. Рассмотрим полезные фишки для работы с ним из прикладной разработки. Работая с данным форматом в прикладной разработке, можно выделить следующие кейсы использования:

  • работа с веб-сервисами – отправка запросов и разбор ответов;
  • хранение данных в структурированном виде.

В данной статье рассмотрим пока только работу с сервисами. Но сначала немного вспомним про MS ScriptControl, так как с помощью него будем работать с JavaScript. Так же к статье прикреплен файл, в котором даны все функции, использующиеся в примерах и даже больше.

MS ScriptControl

MS ScriptControl – интерпретатор от Microsoft, часто используется для возможности встраивания в программу кода на другом языке программирования. Рассмотрим основные свойства и методы:

Language(const String: WideString) - задает язык, интерпретатор которого будет реализовывать компонент. В стандартной поставке доступны VBScript и JScript.

AddCode(const Code: WideString) - добавляет код, заданный параметром к списку процедур компоненты. В дальнейшем эти процедуры могут быть вызваны при помощи метода Run либо из других процедур скрипта.

Eval(const Expression: WideString): OleVariant - выполняет код, заданный в параметре Expression, и возвращает результат исполнения.

Reset - сбрасывает компоненты в начальное состояние, удаляет все добавленные ранее объекты и код.

Run(const ProcedureName: WideString; var Parameters: PSafeArray): OleVariant - выполняет именованную процедуру из числа ранее добавленных при помощи метода AddCode. В массиве Parameters могут быть переданы параметры.

Пример:

ScriptControl = CreateObject("ScriptControl")
ScriptControl.Language = "JScript" 
JsonObject = ScriptControl.Eval('[{"id": "123"}, {"id": "651"}]')
ScriptControl.AddCode(
      'function GetJSONColumn(JsonObj, ParamName) {
          var values = [];
          for(var i in JsonObj) {
              values.push(JsonObj[i][ParamName]);
          }
          return values.join("| ");
      }'
  )
Result = ScriptControl.Run('GetJSONColumn'; JsonObject; 'id')
ShowMessage(Result)

Существует несколько разных способов выполнения программы с помощью ScriptControl. Самый простой способ - метод Eval. Этот метод возвращает значение указанного выражения, позволяет выполнить код без добавления его к списку процедур компонента. Например,

x = scriptControl.Eval('1+2')
ShowMessage(x) // 3

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

Другой способ выполнения кода сценария - использовать метод Run. Этот метод позволяет выполнить любой код, добавленный в ScriptControl с помощью метода AddCode. Во время этого процесса проверяется синтаксис кода, и первая найденная ошибка вызывает событие Error. В параметрах Run указывается имя функции, которую необходимо выполнить, а также список передаваемых параметров, если они есть.

Таким образом, в простых случаях можно использовать Eval, для более сложных лучше воспользоваться другими возможностями ScriptControl.

Работа с сервисами

И так, работать с JavaScript будем через ScriptControl. Рассмотрим еще один полезный пример, который нам пригодится в работе с сервисами: допустим, при отправке запроса к веб сервису необходимо передать параметры. Чтобы избежать неожиданных запросов к серверу, обычно вызывается метод JavaScript encodeURIComponent - для замены символов в любых вводимых пользователем параметрах, используемых как часть URI.  Чтобы реализовать аналог данного метода на ISBL, потребуются определенные усилия и затрата времени, проще воспользоваться уже готовым решением JavaScript (пример во вложении, функция URLEncode()).

 String = 'results=5'
 JS = CreateObject("ScriptControl")
 JS.Language = "JScript"
 Result = JS.Run('encodeURIComponent'; String)
 ShowMEssage(Result)

Теперь перейдем непосредственно к формату JSON. Существуют различные библиотечки для работы с JSON в JavaScript, например, в функциях, которые будут приложены к статье, использовалась библиотека https://cdnjs.cloudflare.com/ajax/libs/json2/20160511/json2.js. Она создает глобальный объект JSON, содержащий два метода: stringify и parse. Как ее использовать в работе: создаем функцию, допустим CreateJavascriptProcessor(), в переменную JSONLib передадим текст кода из этой библиотечки. Далее подключаем ScriptControl:

 JScriptControl = CreateObject("ScriptControl")
 JScriptControl.Language = "JScript"
 JScriptControl.AddCode(JSONLib) 

Добавляем новые функции для работы с JSON, к которым будем обращаться из прикладной (более подробнее см. функцию во вложении): 

 JScriptControl.AddCode(
    "function ToJSON(obj) {
        return JSON.stringify(obj);
    };")
  JScriptControl.AddCode(
    "function FromJSON(jsonstring) {
        return JSON.parse(jsonstring);
    };")
  JScriptControl.AddCode(
    "function AddValueToArray(array, value) {
        array.push(value);
    };") 
  JScriptControl.AddCode(
    "function SetValueInArray(array, index, value) {
        array[index] = value;
    };")
   …  
  Result = JScriptControl

И практически все – можно использовать:

JavascriptProcessor = CreateJavascriptProcessor()
Result = JavascriptProcessor.Run("FromJSON"; JSONString)

Рассмотрим применение формата JSON при отправке POST запросов и получении данных при GET запросах.

Примером отправки POST запроса служит импорт данных об обращениях в раздел «Результаты рассмотрения обращений» информационного ресурса ССТУ.РФ. На портал ССТУ.РФ можно выгружать информацию по обращениям автоматически - на прямую через API сервиса или полуавтоматическим - путем формирования архива, который в последующем вручную загружается на портал. В обоих случаях информация по обращению формируется в виде JSON. При автоматической выгрузке отправляются POST запросы, они должны иметь заголовок Content-Type: application/json, в теле запроса данные в формате JSON в кодировке UTF-8. При полуавтоматической выгрузке формируется архив, который содержит файлы с расширением «.json» и соответственно с данными в формате JSON. В случае успешной загрузки обращения метод возвращает пустой ответ с кодом 204 No Content.

В связи с тем, что доступ к этому порталу закрытый, полностью функциональный пример не приведу, только основные моменты.

Пример тела запроса:

{
  "departmentId": "3DB1BE3B-BED1-49F6-AB4C-D138A81TEST5",
  "isDirect": false,
  "format": "Electronic",
  "number": "А26-00-000000000",
  "createDate": "2018-09-14",
  "name": "Иванов Иван Иванович",
  "address": "г. Ижевск, ул. Пушкинская, д.88",
  "email": "user@hostname.com",
  "questions": [
    {
    "code": "0001.0001.0001.0001",
    "status": "InWork",
    "incomingDate": "2018-09-14",
    "registrationDate": "2018-09-14"
    }
  ]
}

Упрощенный пример формирования и отправки запроса:

// Создать объект JS для работы с JSON
ResultObject = CreateJavascriptObject()
// Добавить пары ключ-значение
SetJSObjectProperty(ResultObject; "departmentId"; departmentId)
SetJSObjectProperty(ResultObject; "isDirect"; isDirect)
SetJSObjectProperty(ResultObject; "format"; "Electronic")
SetJSObjectProperty(ResultObject; "number"; number)
SetJSObjectProperty(ResultObject; "createDate"; createDate)
SetJSObjectProperty(ResultObject; "name"; name)

QuestionsObject = CreateJavascriptObject()
SetJSObjectProperty(QuestionsObject; "code"; code)
SetJSObjectProperty(QuestionsObject; "status"; status)
SetJSObjectProperty(QuestionsObject; "incomingDate"; incomingDate)
SetJSObjectProperty(QuestionsObject; "registrationDate"; registrationDate)

SetJSObjectProperty(ResultObject; "questions"; QuestionsObject)

// Преобразовать объект в формат JSON       
JSONData = ObjectToJSON(ResultObject)    
// Отправить на портал ССТУ
Response = SendHTTPRequestJSON(URL; 'POST'; ''; JSONData)   
if Response == ''
  ShowMessage("Статус по обращению успешно отправлен в ССТУ.") 
else
  // Обработка ситуации если вернулась ошибка
endif

Для примера формирования GET запроса и разбора результата, который получаем в формате JSON, использую один из сервисов с открытым API  - randomuser:

RandomUserURL = "https://randomuser.me/api/"
RandomUserMethod = "GET"
// Задать параметр запроса – ограничить выборку пятью результатами
RequestParams = CreateList()
RequestParams.Add("results"; 5)  
// Отправить запрос
JSONResult = SendHTTPRequestJSON(RandomUserURL; RandomUserMethod; RequestParams)
Me = JSONToObject(JSONResult; TRUE)
// Получить результат по первой персоне
Person = GetJSValueFromArray(Me.results; 0)
ShowMessage(Person.location.street)

С помощью функции GetValueFromArray() можно легко обращаться напрямую к любому элементу ответа и получать нужное значение, указав окончательный путь через точку. В примере наименование улицы получаем путем указания "полного пути" к элементу: Person.location.street.

Заключение

Таким образом, использование формата JSON в прикладной разработке позволяет формировать запросы к веб-сервисам и разбирать их. В следующей статье рассмотрим примеры хранения данных в формате JSON. Надеюсь наработки будут полезны в вашей работе.

Прикреплен файл: function_dir.zip

Михаил Тарасов

Примерно к такому же алгоритму пришел. Только работаю всё же через Eval и возвращаю JS объект, в котором уже есть методы для создания объекта, создания массива, получения доступа к элементу объекта/массива по ключу (getter) и изменения/добавления элемента в объекте/массиве по ключу (setter). Ну и конечно обертка для parse и stringify. 

Так, например, отдельный метод для добавления элемента в массив не нужен, потому что можно push вызывать напрямую, как метод массива.

Использую для всякого. В этой разработке для синхронизации данных из JSON описания проекта в справочники.

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

Отходя от темы JSON'а, ScriptControl используем так же для выполнения регулярок, для выполнения заносимых пользователем формул, как параметризированный формульный калькулятор. Применяем так же в том случае, когда есть Web контрол и в нем есть какое то полезное JS вычисление. Но требуется иногда выполнят его же не имея в наличии формы с Web контролом. Переписывать это вычисление на ISBL такой себе вариант. Выручает ScriptControl. 

Михаил Тарасов: обновлено 02.10.2018 в 09:59
Александр Чугунов

Да, штука очень удобная.
Жаль что для 5.0 у нас оказалась нестабильной. А может у нас слишком много сложных веб-контролов и JScript один на всех. Падает в общем стабильно, JScript какие-то ссылки не отпускает. Весь код перелопатили и много чего пробовали, но чет никак=( 

Михаил Тарасов

Александр, у нас работает стабильно.

Главное соблюдать некоторые правила работы. Выяснил уже давно: Передавать JS объекты, полученные из ScriptControl'а куда-то вне текущего события или вне текущей функции - плохая практика. 

Если вы внутри функции обратитесь к ScriptControl, получите оттуда, например, массив объектов и массив вернете из функции, вне функции он работать не будет. При завершении функции ISBL решит почистить неиспользуемые переменные. Почистит и ScriptControl объект вместе со всеми внутренними элементами. То, что на эти внутренние элементы остались ссылки ему фиолетово. В последствии, при попытке обратится к внутреннему элементу получите ошибку.

Можно было бы ссылки зациклить. Например, объект ScriptControl положить внутрь себя. Тогда, по идее, он не должен уничтожаться, так как на него есть ссылка. но такой подход чреват утечками памяти.

Можно передавать их парами. Объект из JS и вместе с ним его ScriptControl. Но тут самому уже сложно работать с ними...

Поэтому, я сделал для себя вывод. Годится для работы только в одном событии/одном листинге...

Руслан Бапин

А если ссылку на ScriptControl положить в параметры текущего объекта (Params) или в окружение (Environment), пользоваться им в разных событиях, а при закрытии объекта - удалять ScriptControl?

Михаил Тарасов

Руслан, вариант, но тоже с "ограничениями". Проблемные ситуации всё равно могут возникнуть. Конечно, гораздо реже. И, вероятно, основной массив кейсов вы таким решением покроете, но в теории выстрелить в ногу всё равно можно будет. Кстати, самому удалять в этом случае ничего не надо будет. ISBL очистит Object и следом очистит его Params...

 

Дмитрий Бакаев

Выполняя запрос, в функции SendHTTPRequestJSON возникает ошибка "Сбой скачивания указанного ресурса." в 19 строке. Я подозреваю что в одном из параметров, большой объем информации. При уменьшении текста, все срабатывает, но проблема в том что нужно передать весь текст. Кто сталкивался с таким? Как решали проблему?

Авторизуйтесь, чтобы написать комментарий