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:
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!
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.
Check this post.
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!
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, id, forename, 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...
Thank you once again!
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...