Creating complex JSON objects and accessing elements within

Hi,

I have been reading the posts and the documentation around JSON support available with 2016.2 (which is my version) and following versions, but haven't found a concrete example of how to design and implement a complex JSON entity object. I'm working on creating a REST client to connect to an external service and working on modeling the request and response objects, but keep running into invalid OREF when trying to access elements. I've tried creating a container class 'request', which extends %Library.DynamicAbstractObject and has properties tag which is a string and body which is another class that extends %Library.DynamicAbstractObject and contains several collections with defined properties.

Here is an example of the JSON I'm hoping to model into a class:

{
    "tag": "post",
    "body": {
        "patient": {
            "site": [{
                    "code": "string",
                    "mrn": "string"
                }
            ],
            "email": ["string"],
            "addresses": [{
                    "line1": "string",
                    "line2": "string",
                    "city": "string",
                    "state": "string",
                    "postalCode": "string"
                }
            ],
            "names": [{
                    "first": "string",
                    "middle": "string",
                    "last": "string",
                    "suffix": "string"
                }
            ]
        }
    }
}
  • + 1
  • 0
  • 154
  • 6
  • 2

Answers

Check RESTForms project - it aims to do exactly that.

Community article: part 1, part 2.

Stumbled upon a different InterSystems github project that in my testing and with a little configuration works as I expected (https://github.com/stefanc82/Cache-DynamicObject-Adapter). Any thoughts on which project might be the preferred path for future?

I didn't come across this project when I searched github for projects that mention %DynamicObject/%DynamicArray. I'll look into this project as well.

As I'm the author of RESTForms, I'd recommend that. The advantage is that it offers not only JSON<->object transformation, but a complete CRUD REST API for your data.

Calling @Stefan Cronje

I have not looked at RESTForms, so I can only provide a biased opinion on the DynamicObject Adapter.

We have used this for REST CRUD interfaces. This enabled us to use the same message structure classes for SOAP, REST XML and REST JSON interfaces. On the REST interfaces we use the ContentType to determine whether we should use the XML Reader to parse or the DynamicObject adapter. After the parsing, we encapsulate the message instance in an Ensemble message to invoke a business service in the cases where it is not just CRUD.

Another great resource is the REST and Relaxation webinar.  If your external service returns the above JSON body, you can work with the JSON directly. 

Get the JSON from the HTTP Request Body

    ClassMethod GetJSONData() As %DynamicObject
    {
        // Throw our own exception if the request body does not contain valid JSON.
        Try {
            Set obj = ##class(%DynamicObject).%FromJSON(%request.Content)
        } Catch ex {
            Throw ##class(MyProject.Exception.BadRequest).%New("Invalid JSON")
        }
        Quit obj
    }

Call the method from within your %CSP.REST class. The URL and HTTP method will be your event trigger.

Class MyProject.Patient Extends %CSP.REST

 <Routes>
            <Route Url="/postPatient" Method="Post" Call="PostPatient" />
 </Routes>

ClassMethod PostPatient() As Status
{
   Set formData = ##class(MyProject.API.Service).GetJSONData()   
  
   // Get the tag element
  Set tag = formData.tag

  // Loop through a JSON Array
  Set siteIter = formData.patient.site.%GetIterator()
 
  if ( $GET(siteIter)'=""
  {
            while(siteIter.%GetNext(.key, .value)
            {
                  Set code = value.code
                  Set mrn = value.mrn
            }
  }
return $$$OK
}


The syntax here may not be 100% correct but I hope this gives you some ideas. Try to keep your JSON object as simple as possible

When you get the information from the JSON body you can decide whether to create a new object or modify an existing object using the %Persistent methods %OpenId() %ExistsId() and %New(). I would model your classes based on how you wish to store the data contained within the HTTP request rather than the request itself.  If you are able to obtain information from the request itself eg. "tag: post" then you probably do not need to store that in the database.

You can set the response status you wish to return by

Set %response.Status = ..#HTTP400BADREQUEST   

 so you may not need to encode things in JSON that are part of the request.
 

I use AutoMapper and NewtownsoftJson .NET libraries for mapping JSON to C# objects/models and work with the JSON and ObjectScript classes directly when saving JSON data to the database.

Update: There's some useful documentation in the Using JSON in Caché Guide, including JSON Serialization and de-serialization to dynamic entities using %FromJSON and %ToJSON methods

Comments

Since it needs to be completely converted to JSON, my thinking is that each object needs to extend %Library.DynamicAbstractObject. I'm not sure if it is my structure or how I'm trying to access elements. I have a mix of single properties and collections of objects, so this is a great example to have on the community forum for a real world use case. The documentation has examples, but none that I saw that matched the complexity of what I've defined.

Can you put together some code to create the Test.Request object and set/get some of the properties, so we can see how it is supposed to be done?

Class Test.Request Extends %Library.DynamicAbstractObject
{
Property tag As %String;

Property body As Test.Body;

}

Class Test.Body Extends %Library.DynamicAbstractObject
{
Property requestType As List Of %String;

Property patient As Test.Patient;

}

Class Test.Patient Extends %Library.DynamicAbstractObject
{
Property sites As List Of Test.Site;

Property names As List Of Test.Name;

Property addresses As List Of Test.Address;

Property emails As %String;
}

Class Test.Address Extends %Library.DynamicAbstractObject
{
Property street As %String;

Property unit As %String;

Property city As %String;

Property state As %String;

Property zipCode As %String;
}

Class Test.Name Extends %Library.DynamicAbstractObject
{
Property first As %String;

Property middle As %String;

Property last As %String;

Property suffix As %String;
}

Class Test.Site Extends %Library.DynamicAbstractObject
{
Property code As %String;

Property mrn As %String;    
}

%DynamicAbstractObject is not intended to be subclassed by end users. It serves as a base for %DynamicArray and %DynamicObject, which provide JSON-style dynamic objects without a fixed schema.

You seem to be looking for a way to map between JSON and plain old registered objects. We haven't yet released such a feature, but you can do something kind of similar using DocDB, which I think first shipped in IRIS 2018.1:

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

This feature helps to define a class that stores a JSON document in a persistent object and extract fields from the document into properties of the object.