Новые динамические объекты и поддержка JSON в InterSystems Caché

Dynamism of A Dog on a Leash (1912) Giacomo BallaВообще, в InterSystems Caché и динамические объекты, и поддержка JSON есть уже достаточно давно, но в версии 2016.1 они были переосмыслены, а код реализации переведён с COS уровня на уровень ядра/С, что позволило добиться существенного повышения производительности в этих областях. О том, что есть нового и как переходить (а также о том, как сохранить совместимость с предыдущими версиями) я расскажу в этой статье.

Возможности по работе с JSON


И начну с примера. Теперь такой синтаксис — работает и это самое большое нововведение в синтаксисе COS:

Set object = { "property": "val", "property2": 2, "property3": null }
Set array = [ 1, 2, "string", true ]


Как видите JSON теперь является полноправной частью COS. Что же происходит при подобном присвоении? Объект object становится экземпляром класса %Library.Object, а array является экземпляром класса %Library.Array. Они оба являются динамическими объектами.

Динамические объекты


Динамические объекты в Cache были и раньше — в виде класса %ZEN.proxyObject, но теперь код перемещён в ядро, за счёт чего достигнут значительный прирост по скорости. Все классы динамических объектов наследуются от %Library.AbstractObject, который предоставляет следующую функциональность:

  • Получение объекта из JSON строки, потока, файла
  • Вывод объекта в формате JSON в строку или переменную, автоматическое определение формата вывода в зависимости от контекста
  • Запись объекта в формате JSON в файл
  • Запись объекта в глобал
  • Чтение объекта из глобала


Переход от %ZEN.proxyObject


Итак, вы хотите перейти от %ZEN.proxyObject и различных наследников %Collection.AbstractIterator к использованию наследников %Library.AbstractObject? Это несложно и есть несколько методов:

  • Если вас не интересует совместимость с версиями Caché, предшествующими 2016.1 то вдумчивый Ctrl+H — ваш вариант. Помните, что индексы в массивах теперь начинаются с нуля и к названиям системных методов нужно добавлять $
  • Используйте макросы, которые во время компиляции преобразуют код в нужный вид в зависимости от версии Caché. Я уже писал на Хабре вводную статью про макросы и про пример их использования
  • Используйте класс-абстракцию, который оборачивает соответствующие методы


Использование первого метода в общем-то очевидно, а вот на двух других остановимся поподробнее.

Макросы


Примерный код набора макросов, которые в зависимости от наличия %Library.AbstractObject работают либо с новым, либо с прежним классом динамических объектов.

Макросы

#if $$$comClassDefined(»%Library.AbstractObject»)
    #define NewDynObj ##class (%Object).%New ()
    #define NewDynDTList ##class (%Array).%New ()
    #define NewDynObjList $$$NewDynDTList
    #define Insert (%obj,%element) do %obj.$push (%element)
    #define DynObjToJSON (%obj) w %obj.$toJSON ()
    #define ListToJSON (%obj) $$$DynObjToJSON(%obj)
    #define ListSize (%obj) %obj.$size ()
    #define ListGet (%obj,%i) %obj.$get (%i-1)
#else
    #define NewDynObj ##class (%ZEN.proxyObject).%New ()
    #define NewDynDTList ##class (%ListOfDataTypes).%New ()
    #define NewDynObjList ##class (%ListOfObjects).%New ()
    #define Insert (%obj,%element) do %obj.Insert (%element)
    #define DynObjToJSON (%obj) do %obj.%ToJSON ()
    #define ListToJSON (%obj) do ##class (%ZEN.Auxiliary.jsonProvider).%ObjectToJSON (%obj)
    #define ListSize (%obj) %obj.Count ()
    #define ListGet (%obj,%i) %obj.GetAt (%i)
#endif
#define IsNewJSON ##Expression ($$$comClassDefined(»%Library.AbstractObject»))


Использование
Вот такой код:
 Set obj $$$NewDynObj
 Set obj.prop «val»
 $$$DynObjToJSON(obj)
    
 Set dtList $$$NewDynDTList
 Set = 1
 $$$Insert(dtList, a)
 $$$Insert(dtList, «a»)
 $$$ListToJSON(dtList)

В Cache версии 2016.1+ скомпилируется в int такой код:
 set obj ##class(%Library.Object).%New()
 set obj.prop «val»
 obj.$toJSON()
 set dtList ##class(%Library.Array).%New()
 set = 1
 do dtList.$push(a)
 do dtList.$push(«a»)
 dtList.$toJSON()

А в предыдущих версиях в:
 set obj ##class(%ZEN.proxyObject).%New()
 set obj.prop «val»
 do obj.%ToJSON()
 set dtList ##class(%Library.ListOfDataTypes).%New()
 set = 1
 do dtList.Insert(a)
 do dtList.Insert(«a»)
 do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(dtList)

Класс абстракция


Альтернативным вариантом является создание класса, абстрагирующего используемый динамический объект, например:

Класс Utils.DynamicObject

Class Utils.DynamicObject Extends %RegisteredObject
{
/// Свойство,  хранящее настоящий динамический объект
Property obj;
Method %OnNew () As %Status
{
    #if $$$comClassDefined(»%Library.AbstractObject»)
        Set obj  ##class(%Object).%New()
    #else
        Set obj  ##class(%ZEN.proxyObject).%New()        
    #endif
    Quit $$$OK
}
/// Получение динамических свойств
Method %DispatchGetProperty (pProperty As %String) [ Final ]
{
    Quit obj.%DispatchGetProperty(pProperty)
}
/// Установка динамических свойств
Method %DispatchSetProperty (pProperty As %String,  pValue As %String) [ Final ]
{
    Do obj.%DispatchSetProperty(pProperty, pValue)
}
/// Конвертируем в JSON
Method ToJSON () [ Final ]
{
    #if $$$comClassDefined(»%Library.AbstractObject»)
        Write obj.$toJSON()
    #else
        Do obj.%ToJSON()
    #endif
}
}


Использование полностью аналогично обычному классу:

 Set obj ##class(Utils.DynamicObject).%New()
 Set obj.prop «val»
 Do obj.ToJSON()

Что выбирать


Решать вам. Вариант с классом выглядеть привычнее, вариант с макросами будет несколько быстрее за счёт отсутствия промежуточных вызовов. Для проекта MDX2JSON я выбрал вариант с макросами. Переход прошел быстро и безболезненно.

Производительность JSON


Скорость генерации JSON возросла на порядок. В проекте MDX2JSON есть тесты скорости генерации JSON. Скачайте и убедитесь!

Выводы


Новые динамические объекты и улучшения в поддержке JSON позволяют ускорить работу ваших приложений.

Ссылки


» Документация
» Статья на community.intersystems.com о JSON
» Класс Utils.DynamicObject

© Habrahabr.ru