Question
Pavithra Rajamohan · Oct 2, 2020

Advice about JSON conversions and reading outputs from Dynamic Objects

Hi all,

I need some advice about JSON conversions and the outputs. I am being sent a JSON response from an API call and I am struggling to produce an output that I can use from it.

I have used the %FromJSON() method call to take the JSON stream source (pData) and convert it to a Dynamic object (object) but I am a little unsure as to how I can read the dynamic object and use it to extract the values from this and set the values as a property in another class.

The current coding I have for the conversion:

set object = ##class(%DynamicAbstractObject).%FromJSON(pData)

$$$TRACE("object:"_object)

set jnew = object.%Get("forename")

$$$TRACE("jnew:"_jnew)

I have tried %Get() as a way to try and get the values from within the object but this does not return a value.The traces also do not a return a readable value. Any suggestions on how I can read the values within the object and also extract them?

Thanks!

00
0 21 205 2

Replies

The above should work, so the question is, what is the content of pData. Do you can share a sample with us?

Also, you can write the above somewhat shorter too and you can access all the properties either via propertyname or via the %Get() method.

I assume, you have a string like this:

set pData="{""Name"":""Joe"", ""Age"":50}"

the you can do this

set object={}.%FromJSON(pData)
write object.Name // Joe
write object.%Get("Name") // Joe
write pData // {"Name":"Joe", "Age":50}

I don't know what the TRACE macro does, but I would add that you have a couple of ways to visualize the contents of a dynamic object:

USER>read json
{"foo":"bar"}
USER>s object={}.%FromJSON(json)

USER>do object.%ToJSON()
{"foo":"bar"}
USER>write object.%ToJSON()     
{"foo":"bar"}
USER>zwrite object
object={"foo":"bar"}  ; <DYNAMIC OBJECT>

If object.%Get("forename") or object.forename returns "", you can try object.%IsDefined("forename") to see whether that property is in fact defined.

Thanks Jon, unfortunately I get an error when trying this code using Studio for the output. I did however include object.%IsDefined("forename") which was helpful to confirm that  forename was defined.

Thanks Julius - I have added a new reply with a sample of pData.

Thanks Eduard, this was a useful read. In the Iterate method - what is the Path referencing to?

Path is how you access the value.

Thanks all for your suggestions so far! I have tried your suggestions and still not ale to output the values.

A sample of structure of pData is below:

[{"patient":{"guid":"12345","id":12345,"forename":"Joe","surname":"Bloggs","dateOfBirth":"2002-12-10T00:00:00Z"},"visit":[{"guid":45678", "date": "2020-01-10", "reason":"other"}{"guid": 45679, "date": "2020-01-11", "reason": "routine"}],"documentAttachments": [{"guid": "23432","id": 152,"catergory": "notes"}{"guid":"23433","id":153,"catergory": "summary"}]}
{"patient":{"guid":"12332","id":12332,"forename":"Jack","surname":"Evans","dateOfBirth":"2000-01-13T00:00:00Z"},"visit":[{"guid":45600", "date": "2020-02-11", "reason":"routine"}],"documentAttachments": [{"guid": "23411","id": 167,"catergory": "notes"}]}
]

pData may have one or more instances of patient data and within each patient record, there are multiple subsections like Visit and Document attachments which can also have multiple instances.

In all of your suggestions you have suggested to use the WRITE function, but as I am doing this in Studio I am not sure how I can use this to set it to a property within my other class message. Any suggestions on other commands I can use instead of the WRITE?

The problem is obvious, your set object ##class(%DynamicAbstractObject).%FromJSON(pData) won't work, because pData contains an invalid JSON!

1) "visit":[{"guid":45678" <-- surplus QUOTE-char, maybe a typo

2) reason":"other"} {"guid":45679    missing comma between } and {

3) "catergory":"notes"} {"guid":"23433" missing comma between } and {

You  can use https://jsonlint.com/ to check your JSON string.

Sorry - I manually created the sample of pData I shared with you as I couldn't use the original API response so the typo's are all my mistake. Thanks for your tool to check the JSON string - I have been able to confirm that my version of pData is a JSON string.

Any other suggestions as to how I can figure out what the issue is?

According to above sample data, your JSON-Data (pData) is :

- an array of one or more objects

- the object(s) have "patient", "visit" and "documentAttachments" as properties

- the "patient" property is an object and has "guid", "id", "forename", "surname", and "dateOfBirth" as properties

So, to get the "forename" property, you have to do something like:

set object = {}.%FromJSON(pData)  // create a JSON-Object from JSON string
write object.%Get(0).patient.forename  // 

or step by step

set firstArrayItem = object.%Get(0) // first array item is 0
set patient = firstArrayItem.patient // this is the patioent object
write patient.forename  // now you have the name
write patient.surname // and other properties

By the way, the "visit" property is also an array! To list all the "reason" properties, you have to write a loop:

set visit = firstArrayItem.visit  // the first visit 
for i=0:1:visit.%Size()-1 {
  write visit.%Get(i).reason,!
}

Your output will be:

other

routine

I hope, things are now more clear? 

Thank you very much - I've tried a few values and have been able to extract them as expected! smiley

Sorry I have one more question as I've come across a problem again:

I have a property called "NOK" which is part of the "patient" object, however the "NOK" has its own individual properties too - "NOKname", "NOKrelationship", "NOKemail", "NOKtelephone". NOK is also an array as you could have multiple instances of NOK. I understand I can use the loop suggestion to retrieve the arrays but I am struggling to extract the  initial properties of NOKname etc. An example of the patient section is below:

[
 {
  "patient":{
   "guid":"12345",
   "id":12345,
   "forename":"Joe",
   "surname":"Bloggs",
   "dateOfBirth":"2002-12-10T00:00:00Z",
   "NOK": [
                    {
                      "NOKname": "Alison Bloggs",
                      "NOKrelationship": "Wife",
                      "telephone": "02081234567",
                      "email": "alison@bloggs.com",

      }
  ],
 },

I have tried the following an either get an empty value or an undefined value:

set firstArrayItem = {}.%FromJSON(pData) 

set patient = firstArrayItem.patient

set NOK=patient.NOK

set NOKName=NOK.NOKname

which gives me a value like :  29@Library.DynamicObject

If I try not including the patient and going straight to NOK and then NOKName it gives me a blank response

Any suggestions on how I get the values for NOKname etc?

 

It's simple and easy... just follow the picture (I meant, the JSON string):

[
  {
    "patient":
      {
        "guid":"12345",
        "id":12345,
        "forename":"Joe",
        "surname":"Bloggs",
        "dateOfBirth":"2002-12-10T00:00:00Z",
        "NOK":[
                {
                   "NOKname":"Alison Bloggs",
                   "NOKrelationship":"Wife",
                   "telephone":"02081234567",
                   "email":"alison@bloggs.com"
                }
              ]
      }
  }
]

Now we create a dynamic Object:

set obj = {}.%FromJSON(pData)

Your pData is a JSON-Array, where the (array)elements/items are objects

 (we have just one elemment), so we can say:

set item = obj.%Get(0)

This  item object has a patient property, which is an object, so we go one step deeper

set item = obj.%Get(0)
set patient = item.patient 
set patient = obj.%Get(0).patient  // same as above

Our patient object has properties like guid,  idforename, surname, dateOfBirth and NOK

The property NOK itself is, again, an array where the array elements/items are objects.

set nok = patient.NOK
set nok = obj.%Get(0).patient.NOK // same as above

Now, we know, our property NOK is an array, so we have to take of those elements. This nok elements are objects, so we can take the properties (NOKname, ...email):

set nokItem = nok.%Get(0)
write nokItem.NOKname," ",nokItem.email,!

or, all in one line

write obj.%Get(0).patient.NOK.%Get(0).NOKname,!

Now all together, a small routine to print out all NOKxxxx properties:

set obj = {}.%FromJSON(pData)
for i=0:1:obj.%Size()-1 { // loop over pData items grabbing patient props
    set patient = obj.%Get(i).patient
    for j=0:1:patient.NOK.%Size()-1 { // now we loop over all NOK items
        set nok = patient.NOK.%Get(j)
        write nok.NOKname,!
        write nok.NOKrelationship,!
    }
}

So I hope, you see the light at the end of the tunnel...

Hey Julius,

Sorry I have another query related to this so not sure if I should post it here or as another question so putting here for now.

We have done the extractions and loops and used it to set to some variables. The variables are then used to create a HL7 message. For the values which are extracted from the loop we find they are over-writing the value from> The coding so far:
NOK name - We are doing the following to set the value to a variable:

set NOKName = object.%Get(i).referral.nextOfKin.%Get(k).name

we are then setting the NOKname into a HL7 segment as follows as part of the same loop:

d RefMessage.SetValueAt("1", "NK1:SetIDNK1")
d RefMessage.SetValueAt(NOKname, "NK1(1):Name")

With this code we would like it whereby each time there is a new NOKname then a new segment value or a new segment is added to the HL7 message rather than over-writing the first value from the loop. Do you have any ideas how that would be possible?

Unfortunately no, because I have nothing to do with HealthShare, HL7 and all those healthy things. If you have any ObjectScript or Class problem, I am happy to help you.
 

Hi Julius,

I may have posed my question incorrectly but if I leave the HL7 part out, the issue I have is for the repeated array objects like NOKName . An example of pData is as follows:

[
  {
    "patient":
      {
        "guid":"12345",
        "id":12345,
        "forename":"Joe",
        "surname":"Bloggs",
        "dateOfBirth":"2002-12-10T00:00:00Z",
        "NOK":[
                {
                   "NOKname":"Alison Bloggs",
                   "NOKrelationship":"Wife",
                   "telephone":"02081234567",
                   "email":"alison@bloggs.com"
                }
                {
                   "NOKname":"Peter Bloggs",
                   "NOKrelationship":"Father",
                   "telephone":"02081234567",
                   "email":"Peter@bloggs.com"
                }
              ]
      }
  }
]

The code I have used is:

Method GetDataFromStream(pData As %Stream)
{

    set object={}.%FromJSON(pData)
    set size1=object.%Size()
    set i=0
    for i=0:1:size1-1
    {
        
        set MyGuid=object.%Get(i).patient.guid
        set Myid=object.%Get(i).patient.id
        set MyForename=object.%Get(i).patient.forename
        set MySurname=object.%Get(i).patient.surname
        set MyDOB=object.%Get(i).patient.dateOfBirth
    
        set j=0
        set NOK = object.%Get(i).NOK
        for j=0:1:ref.NOK.%Size()-1  
        {
            set MyNOKname=NOK.%Get(j).NOKname
        }
    }
}

The problem is the variable "MyNOKName" only stores last value i.e. "Peter Bloggs". I would like to be able to store every instance of the "NOKName"  in a seperate variable based on the index(j) and use all of them at a later stage in the method. For instance MyNOKName(1) = "Alison Bloggs", MyNOKName(2)="Peter Bloggs"

Are you able to let me know if there is a way to do that?

Yes, you can store the values in a multidimensional array. And the syntax you showed is correct:

for j=0:1:ref.NOK.%Size()-1  
        {
            set MyNOKname(j)=NOK.%Get(j).NOKname
        }

To say it simply, you did this:

set x=123
set x=456

and just  see 456 but not 123!

What you (probably) want to do is:

set x(1)=123
set x(2)=456

I added one more "patient item" to your JSON and a simple test class

Class DC.PR1 Extends %RegisteredObject
{ 
ClassMethod GetDataFromTestStream() [ PublicList = (myGuid, myId, myForename, mySurname, myDOB, nokName, nokRelation, nokTel, nokEmail) ]
{
   set xData=##class(%Dictionary.CompiledXData).%OpenId(..%ClassName(1)_"||Test")
   try { set obj={}.%FromJSON(xData.Data) }
   catch e { set obj="",err=e.Name }
   if 'obj { 
      write "Error: ",err,!
      quit ""
   }  

   // Put all properties in the corresponding SUBSCRIPTED variable
   //
   for i=0:1:obj.%Size()-1 { // loop over patients (top array)
      set patient=obj.%Get(i).patient

      set myGuid(i)=patient.guid
      set myId(i)=patient.id
      set myForename(i)=patient.forename
      set mySurname(i)=patient.surname
      set myDOB(i)=patient.dateOfBirth

      for j=0:1:patient.NOK.%Size()-1 { // loop over NOKs of the i-th patient
         set nok=patient.NOK.%Get(j)

         set nokName(i,j)=nok.NOKname
         set nokRelation(i,j)=nok.NOKrelationship
         set nokTel(i,j)=nok.telephone
         set nokEmail(i,j)=nok.email

         // instead of storing each property, you could
         // process them hier...
      }
   }
} 

XData Test [ MimeType = application/json ]
{
[
  {
    "patient":
      {
        "guid":"12345",
        "id":12345,
        "forename":"Joe",
        "surname":"Bloggs",
        "dateOfBirth":"2002-12-10T00:00:00Z",
        "NOK":[
                {
                   "NOKname":"Alison Bloggs",
                   "NOKrelationship":"Wife",
                   "telephone":"02081234567",
                   "email":"alison@bloggs.com"
                },
                {
                   "NOKname":"Peter Bloggs",
                   "NOKrelationship":"Father",
                   "telephone":"02081234567",
                   "email":"Peter@bloggs.com"
                }
              ]
      }
  },
  
  {
    "patient":
      {
        "guid":"212345",
        "id":212345,
        "forename":"John",
        "surname":"Blue",
        "dateOfBirth":"2003-12-10T00:00:00Z",
        "NOK":[
                {
                   "NOKname":"Aline Blue",
                   "NOKrelationship":"Wife",
                   "telephone":"032081234567",
                   "email":"aline@blue.com"
                },
                {
                   "NOKname":"Peter Blue",
                   "NOKrelationship":"Father",
                   "telephone":"032081234567",
                   "email":"Peter@blue.com"
                }
              ]
      }
  }

]
} 

}


Try it in a terminal session:

do ##class(DC.PR1).GetDataFromTestStream()

zwrite

The output  should be:

myDOB(0)="2002-12-10T00:00:00Z"
myDOB(1)="2003-12-10T00:00:00Z"
myForename(0)="Joe"
myForename(1)="John"
myGuid(0)=12345
myGuid(1)=212345
myId(0)=12345
myId(1)=212345
mySurname(0)="Bloggs"
mySurname(1)="Blue"
nokEmail(0,0)="alison@bloggs.com"
nokEmail(0,1)="Peter@bloggs.com"
nokEmail(1,0)="aline@blue.com"
nokEmail(1,1)="Peter@blue.com"
nokName(0,0)="Alison Bloggs"
nokName(0,1)="Peter Bloggs"
nokName(1,0)="Aline Blue"
nokName(1,1)="Peter Blue"
nokRelation(0,0)="Wife"
nokRelation(0,1)="Father"
nokRelation(1,0)="Wife"
nokRelation(1,1)="Father"
nokTel(0,0)="02081234567"
nokTel(0,1)="02081234567"
nokTel(1,0)="032081234567"
nokTel(1,1)="032081234567"

And don't forget, you may have more then one "patient" item in your JSON, so either consume the data in the inner loop oder place them in a two-dimensional array.

HTH

To make your (and others) life easier... here is a simple class to display a dynamic object (or dynamic array).

/// A general class for various helper functions
/// 
Class %zapi.utils Extends %RegisteredObject
{ /// Show an dynamic array or object
/// 
/// obj : the dynamic object (or array) you want to display
/// deep: max path depth, 0 (default) = display all
/// find: if given, (a part of) a property name
ClassMethod ShowObj(obj, deep = 0, find = "")
 {
   new %seen
   do ..shObj(obj,deep-1,find,"<obj>")
 }
ClassMethod shObj(obj, deep, find, path) [ Internal, Private ]
 {
   set path=path_"."
   set itr=obj.%GetIterator()
   while itr.%GetNext(.prop,.val) {
      set:obj.%IsA("%DynamicArray") prop="%Get("_prop_")"
      set prop=path_prop
      if $isobject(val) {
         if $data(%seen(+val)) { set val="same as --> "_%seen(+val)
         } else { set %seen(+val)=prop }
      }
      if $isobject(val),deep { do ..shObj(val,deep-1,find,prop)
      } else { write:prop[find prop,": ",val,! }
   }
 }
}

With our old pData,  use it as follows:

write pData 
[{"patient":{"guid":"12345","id":12345,"forename":"Joe","surname":"Bloggs","dateOfBirth":"2002-12-10T00:00:00Z"},"visit":[{"guid":45678,"date":"2020-01-10","reason":"other"},{"guid":45679,"date":"2020-01-11","reason":"routine"}],"
documentAttachments":[{"guid":"23432","id":152,"catergory":"notes"},{"guid":"23433","id":153,"catergory":"summary"}]}]

set obj={}.%FromJSON(pData)
do ##class(%zapi.utils).ShowObj(obj)

If you don't want to write each time a novel, you can create your own "show object" command too.
Insert the following line into %ZLANGC00 routine (if %ZLANG doesn't exists, create it):

ZSHO(obj, deep=0, find="") Public { do ##class(%zapi.utils).ShowObj(obj,deep,find) }

and Voila!, you got a short command to display dynamic objects

zsho obj              // display the whole object
zsho obj:2            // display up to  second nesting level
zsho obj::"visit"     // display all occurrences of property visit
zsho obj::"guid"      // display all occurrences of property guid

The outputs of the above commands are

USER>zsho obj
<obj>.%Get(0).patient.guid: 12345
<obj>.%Get(0).patient.id: 12345
<obj>.%Get(0).patient.forename: Joe
<obj>.%Get(0).patient.surname: Bloggs
<obj>.%Get(0).patient.dateOfBirth: 2002-12-10T00:00:00Z
<obj>.%Get(0).visit.%Get(0).guid: 45678
<obj>.%Get(0).visit.%Get(0).date: 2020-01-10
<obj>.%Get(0).visit.%Get(0).reason: other
<obj>.%Get(0).visit.%Get(1).guid: 45679
<obj>.%Get(0).visit.%Get(1).date: 2020-01-11
<obj>.%Get(0).visit.%Get(1).reason: routine
<obj>.%Get(0).documentAttachments.%Get(0).guid: 23432
<obj>.%Get(0).documentAttachments.%Get(0).id: 152
<obj>.%Get(0).documentAttachments.%Get(0).catergory: notes
<obj>.%Get(0).documentAttachments.%Get(1).guid: 23433
<obj>.%Get(0).documentAttachments.%Get(1).id: 153
<obj>.%Get(0).documentAttachments.%Get(1).catergory: summary

USER>zsho obj:2
<obj>.%Get(0).patient: 11@%Library.DynamicObject
<obj>.%Get(0).visit: 2@%Library.DynamicArray
<obj>.%Get(0).documentAttachments: 21@%Library.DynamicArray

USER>zsho obj::"visit"
<obj>.%Get(0).visit.%Get(0).guid: 45678
<obj>.%Get(0).visit.%Get(0).date: 2020-01-10
<obj>.%Get(0).visit.%Get(0).reason: other
<obj>.%Get(0).visit.%Get(1).guid: 45679
<obj>.%Get(0).visit.%Get(1).date: 2020-01-11
<obj>.%Get(0).visit.%Get(1).reason: routine

USER>zsho obj::"guid"
<obj>.%Get(0).patient.guid: 12345
<obj>.%Get(0).visit.%Get(0).guid: 45678
<obj>.%Get(0).visit.%Get(1).guid: 45679
<obj>.%Get(0).documentAttachments.%Get(0).guid: 23432
<obj>.%Get(0).documentAttachments.%Get(1).guid: 23433 

Have a nice day...