%JSONImport: How to import JSON object with an array item that can be "anyof" 3 different schemas
Hello,
I am writing a POST API using IRIS. My POST API Endpoint invokes a Business Service -> Business Process -> Business Operation in an IRIS production .
I am trying to import the JSON payload into a JSON enabled class and work with the JSON class in my Business Process and invoke different Business operation(s) based on the data supplied. This works fine for simpler JSON schemas.
The POST API I am writing now needs to handle a complex schema. I.e. one of the Item on my JSON schema ("recipient") can be an array of "anyof" 5 different schemas.
e.g.
Here, recipient can be an array of any of the 5 different schemas like Reference, Contact, Patient, Practitioner or PractitionerRole.
So in my JSON enabled class, representing this particular schema, I tried defining Property recipient as list of %DynamicObject; but when I then try to import the JSON object in this class using %JSONImport, it fails because %DyanmicObject doesn't have %JSONNew method.
Has anyone done anything similar before? What should I use as "type" for my recipient property ?
Appreciate any ideas or input.
Regards,
Utsavi
Can you please give us an example for (each) those "variantes" (I mean, those JSON strings)?
Something like:
{"sent":"2021-06-10 09:00:00", "received":"2021-06-10 09:05:00", variante1... } {"sent":"2021-06-10 09:00:00", "received":"2021-06-10 09:05:00", variante2... }
Thank you.
This is a little messy and I'm going to report part of the answer as a bug internally. But regardless, here's one way to make it work - in short, have all of the things that could be listed as a recipient extend a common parent class, and in that class override %JSONNew to detect which type it is.
Output is:
Only problem is, %JSONNew (as advertised in class reference documentation) should get the %DynamicObject representing the object itself, not the parent %DynamicObject. This would only really work if each type is used in exactly one context like this, which seems unlikely.
@Utsavi Gajjar in case you end up reaching out to InterSystems Support about this (which I'd recommend if you're blocked on the issue), the internal Jira reference for the wrong level of %DynamicObject being passed to %JSONNew is DP-406169
Hi
I believe that I have a solution for this.
I worked on the basis that there is a 'Parent' object that has a property Schemas of type %ArrayOfObjectsWithClassName. This allows you to create an array of Objects where 'key' is the schema name and the 'id' is the instance.%Oid()
I then defined 4 classes:
Reference, Contact, Patient, Practitioner
I then created a method to Build N instances of the ParentClass. That code reads as follows:
{
set tSC=$$$OK
set array(1)="DFI.Common.JSON.Contact"
set array(2)="DFI.Common.JSON.Patient"
set array(3)="DFI.Common.JSON.Practitioner"
set array(4)="DFI.Common.JSON.Reference"
try {
for i=1:1:pCount {
set obj=##class(DFI.Common.JSON.ParentClass).%New()
set obj.Schemas.ElementType="%Persistent"
set count=$r(10)
for j=1:1:count {
set k=$r(4)+1
set schema=$classmethod(array(k),"%New"),tSC=schema.%Save() quit:'tSC
do obj.Schemas.SetObjectAt(schema.%Oid(),$p(array(k),".",4))
}
set tSC=obj.%Save() quit:'tSC
}
}
catch ex {set tSC=ex.AsStatus()}
write !,"Status: "_$s(tSC:"OK",1:$$$GetErrorText(tSC))
quit tSC
}
Initially I wanted to see if I could (a) insert different object types into the Array and (b) Export the Parent Object to JSON and so to make life easier I specified [ initialexpression = {some expression}] to generate a value for the field. Sort of like %Populate would do but I didn't want to pre-create instances in the 4 schema tables and then manually go and link them together.
When I ran my Method to create 10 Parents it created them and as you can see in the logic I generate a random number of schemas.
That all worked and I then exported to JSON to String resulting in this:
{"%seriesCount":"1","parentId":"Parent36","parentName":"Example: 38","schemas":{"Contact_1":{"%seriesCount":"1","contactGivenName":"Zeke","contactSurname":"Zucherro"},"Contact_11":{"%seriesCount":"1","contactGivenName":"Mark","contactSurname":"Nagel"},"Contact_3":{"%seriesCount":"1","contactGivenName":"Brendan","contactSurname":"King"},"Contact_8":{"%seriesCount":"1","contactGivenName":"George","contactSurname":"O'Brien"},"Patient_10":{"%seriesCount":"1","patientId":"PAT-000-251","patientDateOfBirth":"2021-05-05T03:38:33Z"},"Patient_2":{"%seriesCount":"1","patientId":"PAT-000-401","patientDateOfBirth":"2017-09-30T21:56:00Z"},"Patient_4":{"%seriesCount":"1","patientId":"PAT-000-305","patientDateOfBirth":"2019-04-19T14:04:11Z"},"Patient_5":{"%seriesCount":"1","patientId":"PAT-000-366","patientDateOfBirth":"2017-07-03T18:57:58Z"},"Patient_7":{"%seriesCount":"1","patientId":"PAT-000-50","patientDateOfBirth":"2016-11-26T03:39:36Z"},"Patient_9":{"%seriesCount":"1","patientId":"PAT-000-874","patientDateOfBirth":"2019-03-28T15:22:37Z"},"Practitioner_6":{"%seriesCount":"1","practitionerId":{"%seriesCount":"1","practitionerId":"PR0089","practitionerTitle":"Dr.","practitionerGivenName":"Angela","practitionerSurname":"Noodleman","practitionerSpeciality":"GP"},"practitionerIsActive":false}}}
Because I am using effectively an array of Objects the array is subscripted by 'key' and so if there are multiple instances of say "Patient" then each instance of "Patient" would over write the existing "Patient" in the array and so in creating the array I concatenated the counter 'j' to the Schema Name.
in object terms if you open an Instance of ParentClass and you use the GetAt('key') method on the Schemas array you will be returned with a full object Oid() and from that you can extract the ClassName and the %Id()
The only way I can see around not having to uniquely identify the 'Schema' %dynamicObject in the JSON string is in the Parent class you need to have an array for each schema type. i.e. Array of Patient, Array of Contact.
In terms of nesting you will see that Patient has a Practitioner and Practioner is linked to a Table of Practitioners and in the JSON above you can see that it picks up the Patient, Practitioner and the Practitioner Details from the Table Practitioners
I havent tried importing the JSON as I would have to remove all of the code that I put in the Schema classes to generate values if the field is NULL but that can be overcome by setting the attribute %JSONIGNORENULL to 0 and then make sure that you specify NULL for the property that has no value.
I would carry on experimenting but we are in the middle of a Power Cut (Thank you South African State Utility Company)
If you want to see the classes I wrote and play with them let me know and I'll email them as I can't upload them
Nigel
Hi
I should have included the class definition for Parent
{ Property ParentId As %String(%JSONFIELDNAME = "parentId", %JSONIGNORENULL = 1, %JSONINCLUDE = "INOUT") [ InitialExpression = {"Parent"_$i(^Parent)} ];
Property ParentName As %String(%JSONFIELDNAME = "parentName", %JSONIGNORENULL = 1, %JSONINCLUDE = "INOUT") [ InitialExpression = {..ParentName()} ];
Property Schemas As %ArrayOfObjectsWithClassName(%JSONFIELDNAME = "schemas", %JSONIGNORENULL = 1, %JSONINCLUDE = "INOUT", CLASSNAME = 2, ELEMENTQUALIFIED = 1, REFELEMENTQUALIFIED = 1);
ClassMethod ParentName() As %String
{
quit "Example: "_$i(^Example)
}
ClassMethod BuildData(pCount As %Integer = 1) As %Status
{
set tSC=$$$OK
set array(1)="DFI.Common.JSON.Contact"
set array(2)="DFI.Common.JSON.Patient"
set array(3)="DFI.Common.JSON.Practitioner"
set array(4)="DFI.Common.JSON.Reference"
try {
for i=1:1:pCount {
set obj=##class(DFI.Common.JSON.ParentClass).%New()
set obj.Schemas.ElementType="%Persistent"
set count=$r(12)
for j=1:1:count {
set k=$r(4)+1
set schema=$classmethod(array(k),"%New"),tSC=schema.%Save() quit:'tSC do obj.Schemas.SetObjectAt(schema.%Oid(),$p(array(k),".",4)_"_"_j)
}
quit:'tSC
set tSC=obj.%Save() quit:'tSC
}
}
catch ex {set tSC=ex.AsStatus()}
write !,"Status: "_$s(tSC:"OK",1:$$$GetErrorText(tSC))
quit tSC
}
Nigel
And one more thing, I created the classes as %Persistent because I wanted to see the generated data and because I am referencing the Oid() in the array of Shcema's those classes has to be persistent as well.
Nigel
Hi
I have done some more experienting and in the Contact class I have a ContactPhoneNumbers which I defined as %ListOfDataTypes and I noticed that they were being generated but not exported to JSON so I changed the type to %ArrayOfDataTpes and that didn't work either. I played around with the %JSON attributes to no avail. I read the documentation on the %JSON.Adapter class and there are strict rules about Arrays and Lists must contain literals or objects and so I wrapped the Phone Numbers in quotes even though I was generating them as +27nn nnn nnn but that made no difference. I suspect that the Attribute ElementType should be set. In the ParentClass I specify that the array of object Oid's has an ElementType of %Persistent (the default is %RegisteredObject) and I think that I should do the same with the Phone Number array/list.
Nigel
Hi Nigel,
Thank you for taking time out to find a solution for this.I'd certainly follow your lead. Is it possible to email me the classes at utsavi.gajjar@mater.org.au ? Thanks again.
Regards
Hi Utsavi
I have sent you an email with the classes.
I have managed to get all of the scemas to be generated correctly and exported to JSON correctly. the only issue is that the if I just use the class name as the 'key' in the array you will only get one instance of each schema however if I append the counter to the classname then it will create multiple instances of each schema in the class
I haven't explored the XDATA mapping associated with %JSON nor investigated the other %JSON classes.
One solution if you were importing the json would be to pre-process the json string and remove the counter appended to the class name but that is a bit cludgy
Nigel
Hi Utsavi
How are you doing with this challenge. Have you got a working solution yet? If so please can I have a look. If I have managed to help get you to where you need to be please mark one of my replies as an acceted answer
Thanks
Nigel