Binding a "regular" Cache object to %DynamicObject and vice versa

Recently, a partner company started to develop an Angular client for their Cache application. Together, we decided to leverage the power of Caché dynamic objects to exchange JSON encoded data between client and server parts. However, we realized that currently there is a gap in Cache JSON implementation that prevents simple use of traditional registered and persistent classes to exposed their data with the same ease as with XML. I wrote a small JSON adapter, that does the job and bridgers the gap. It's purpose is simple expose data described by a regular Cache class in a one-to-one fashion to a %DynamicObject. On the other hand, when a serialized JSON data comes in, it can be easily deserialized into dynamic object and subsequently bound to regular class by the newly created adapter. You can find the adaptor class in the github repository here - https://github.com/dkutac/cache-json-regular-vs-dynamic-object

Here is how it works:

Extend your class with kutac.JSON.Adapter.

The adapter class injects two methods to your class. One, called %Bind, and the second, called %Expose.

Let's have a closer look that them now.

%Expose() - use this method when you wish to expose your regular Cache class as dynamic object, so you can then manipulate the content of JSON easily by means of %DynamicObject class. For instance, if your class has references to other classes or holds a reference to collections, you can easily filter the data to be serialized by dynamic object API before you call %ToJSON() method.

An example of using %Expose method: suppose your regular class is MyApp.Person extends (%Persistent, kutac.JSON.Adapter). You can then instantiate a the class and expose (eventually filter unwanted data out) and serialize.

set person=##class(MyApp.Person).%OpenId(123)

set JSON=person.%Expose().%ToJSON()

write JSON

simple, isn't it?

 

%Bind() - use this method when you need to process incoming JSON data. The adapter is written in such way, that it first tries to identify - based on data received - whether there is already an existing instance of the class, if the class to be bound is persistent. If it fails opening existing class instance, it creates a new instance.

The adapter works with default generated IDs as well as with custom ones, based on IdKey index definition when defined. Just make sure that you supply all values needed for unique identification of the class instance. Please note, that for default IDs we use "_id" key name. It is expected that key names reflect exactly names of properties in bound classes!

suppose you have following JSON string coming:

set json={"_id":"2", "Name":"Joe", "DOB":"2001-01-29"}

then, you can bind the data to the MyApp.Person class and save it this way:

set person=##class(MyApp.Person).%Bind({}.%FromJSON(json),.sc)

$$$ThrowOnError(sc)

$$$THROWONERROR(sc,person.%Save())

As you can see, using the adapter is easy, it doesn't by purpose, offer any parameters to customize names of json key/value pairs. As the adapter is a fresh new development, there may be errors, so please feel free to fix them and contribute to the github.

One final note: this adapter has by no means any ambitions to replace and future functionality of Cache, but shall make you life easier for now and make it easy to switch to system provided API when it comes with some future version of Cache.

 

 

  • + 13
  • 1
  • 1164
  • 7

Comments

Well done, this is a great ideia.

This would have been very useful in our Mojo application. laugh

Hi Daniel!

I tend to look at REST services as a SOA Web Service an, as such, it must have a "contract". Binding this contract to the internal implementation can be problematic. Normally, you would try to work with a JSON object that is more "natural" to your web application while dealing with CRUD operations related to it on the server. That would allow you to decouple the client from the server through the contract and change your server implementation while keeping you client untouched.

So, beware that this excessive coupling can indeed increase productivity right now but may become a nightmare in the future...

Kind regards,

AS

I agree with @Amir Samary. Even though by using inheritance you're allowed to use method generators, it's still against against the SIngle Responsibility Principle, since the code is binding directly to the persistence (which it's SRP should be to work as a storage representation).

Binding another responsibility to it could mean  that sometime in the future the persistence would need to be rewritten to match the newer requirements, thus risking to break current applications and elevating technical debt.

So my golden rule is: unless you're 120% sure that the result generated by the binded class won't change, or you're 120% sure that it still submits to the SRP, only then you can "adapt" the persistent class.

But before that, I would recommend (priority order):

1 - Use an utility method that takes the binding class name and returns the JSON or vice-versa. Can take an optional configuration object or flags to overwrite and determine the serialization behavior for the current case.

 

2 - Create a mirror class that extends exclusively from the Adapter.

 

This keeps the persistent class clean and still allows it to be serialized.

Also, there's a method for doing what I suggest on 1, I think it's been discussed here already.

Hi,

This is a very usefull article, thank you!

But it seems strange that Cache does not support exporting  classes to JSON in a native way.

We can use  %ZEN.Auxiliary.altJSONProvider , but for some reason, %Date properties are not handled

correctly - they are not converted to ODBC format. at least in Cache 2016.2

is this issue fixed in later Cache Versions?

Thanks,

Nael

Hi,

This is really nice function! one quick question :  does this support nested json string? for example:

"{""name"": ""shannon yang"",

""key1"":[

{""key2"": {""key3"":""value3""}},

{""key2"":{""key3"":""value4""}}

   ]

}"

Thanks,

Shannon