Вообще, в 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 работают либо с новым, либо с прежним классом динамических объектов.
#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 a = 1
$$$Insert(dtList,a)
$$$Insert(dtList,"a")
$$$ListToJSON(dtList)
В Cache версии 2016.1+ скомпилируется в int такой код:
set obj = ##class(%Library.Object).%New()
set obj.prop = "val"
w obj.$toJSON()
set dtList = ##class(%Library.Array).%New()
set a = 1
do dtList.$push(a)
do dtList.$push("a")
w dtList.$toJSON()
А в предыдущих версиях в:
set obj = ##class(%ZEN.proxyObject).%New()
set obj.prop = "val"
do obj.%ToJSON()
set dtList = ##class(%Library.ListOfDataTypes).%New()
set a = 1
do dtList.Insert(a)
do dtList.Insert("a")
do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(dtList)
Класс абстракция
Альтернативным вариантом является создание класса, абстрагирующего используемый динамический объект, например:
{
/// Свойство, хранящее настоящий динамический объект
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
Автор: InterSystems