JSON Parsing Structure Code Update Request
Hello All,
I need help Integrating the vendor-provided code into the current code to check if the data is an array and if it is, iterate through each item using a for each loop. Also, I need to hit every error handler code mentioned at the bottom along with the transform in both instances.
My Current Code:
/// Given a patient number in a JSON string format, this includes required transformations and makes use of
/// the MPI Query Handler to pull basic demographics from Epic.
Class CUH.Proc.DCIQGetPatient Extends Ens.BusinessProcessBPL [ ClassType = persistent, ProcedureBlock ]
{
Storage Default
{
<Type>%Storage.Persistent</Type>
}
/// BPL Definition
XData BPL [ XMLNamespace = "http://www.intersystems.com/bpl" ]
{
<process language='objectscript' request='CUH.Mess.DCIQ.JsonIn' response='CUH.Mess.DCIQ.JsonOut' height='3400' width='2000' >
<context>
<property name='JsonObjectIn' type='CUH.Mess.DCIQ.JsonInObj' instantiate='1' />
<property name='HL7QRY' type='EnsLib.HL7.Message' instantiate='0' />
<property name='HL7ADR' type='EnsLib.HL7.Message' instantiate='0' />
<property name='JsonObjectOut' type='CUH.Mess.DCIQ.JsonOutObj' instantiate='1' />
<property name='RequestNumber' type='%String' initialexpression='""' instantiate='0' >
<parameters>
<parameter name='MAXLEN' value='50' />
</parameters>
</property>
<property name='IsInteger' type='%Boolean' instantiate='0' />
<property name='ProcError' type='%String' initialexpression='""' instantiate='0' >
<annotation><![CDATA[Error message to be sent back in the Response.]]></annotation>
<parameters>
<parameter name='MAXLEN' value='50' />
</parameters>
</property>
<property name='ValidOriginUrl' type='%String' instantiate='0' >
<parameters>
<parameter name='MAXLEN' value='100' />
</parameters>
</property>
<property name='ValidContactType' type='%String' instantiate='0' >
<parameters>
<parameter name='MAXLEN' value='50' />
</parameters>
</property>
<property name='ValidIDNumberType' type='%String' initialexpression='""' instantiate='0' >
<parameters>
<parameter name='MAXLEN' value='50' />
</parameters>
</property>
<property name='ValidNHSCodeType' type='%String' initialexpression='""' instantiate='0' >
<parameters>
<parameter name='MAXLEN' value='50' />
</parameters>
</property>
</context>
<sequence xend='200' yend='3150' >
<scope xpos='200' ypos='250' xend='200' yend='2500' >
<annotation><![CDATA[Handles all errors]]></annotation>
<code name='Initialize JSON request' xpos='200' ypos='350' >
<![CDATA[ set context.JsonObjectIn = ##class(CUH.Mess.DCIQ.JsonInObj).%New()
do context.JsonObjectIn.%JSONImport(request.bodyJson)]]>
</code>
<assign name="Valid URL" property="context.ValidOriginUrl" value="##class(Ens.Util.FunctionSet).Lookup("CUH.DCIQToEpic.OriginUrl",$NAMESPACE,"",3)" action="set" xpos='200' ypos='450' >
<annotation><![CDATA[Environment specific]]></annotation>
</assign>
<assign name="Valid ContactType" property="context.ValidContactType" value="##class(Ens.Util.FunctionSet).Lookup("CUH.DCIQToEpic.ContactType",$NAMESPACE,"",3)" action="set" xpos='200' ypos='550' >
<annotation><![CDATA[Environment specific]]></annotation>
</assign>
<assign name="Valid IDNumberType" property="context.ValidIDNumberType" value="##class(Ens.Util.FunctionSet).Lookup("CUH.DCIQToEpic.IDNumberType",$NAMESPACE,"",3)" action="set" xpos='200' ypos='650' >
<annotation><![CDATA[Environment specific]]></annotation>
</assign>
<assign name="Valid NHSCodeType" property="context.ValidNHSCodeType" value="##class(Ens.Util.FunctionSet).Lookup("CUH.DCIQToEpic.NHSCodeType",$NAMESPACE,"",3)" action="set" xpos='200' ypos='750' >
<annotation><![CDATA[Environment specific]]></annotation>
</assign>
<if name='Check OriginUrl' condition='context.JsonObjectIn.meta."origin_url"=context.ValidOriginUrl' xpos='200' ypos='850' xend='200' yend='2250' >
<true>
<if name='Check Contact Type' condition='(context.JsonObjectIn.data."0"."con_type"=context.ValidContactType)||(context.JsonObjectIn.data."con_type"=context.ValidContactType)' xpos='470' ypos='1000' xend='470' yend='2150' >
<true>
<if name='Check idNumber Type' condition='(context.JsonObjectIn.data.idNumbers.GetAt(1).type)=context.ValidIDNumberType' xpos='740' ypos='1150' xend='740' yend='2050' >
<true>
<assign name="Get Request Number" property="context.RequestNumber" value="context.JsonObjectIn.data.idNumbers.GetAt(1).number" action="set" xpos='1010' ypos='1300' />
<code name='Integer pattern' xpos='1010' ypos='1400' >
<![CDATA[ set context.IsInteger = ((context.RequestNumber)?.N)]]>
</code>
<if name='Integer?' condition='context.IsInteger' xpos='1010' ypos='1500' xend='1010' yend='1950' >
<annotation><![CDATA[Checks if Requested Number sent is an Integer]]></annotation>
<true>
<transform name='Create HL7 query' class='CUH.Tran.GetDCIQJsonPatientToEpicQRYQ011' source='context.JsonObjectIn' target='context.HL7QRY' xpos='1280' ypos='1650' />
<call name='Send to MPIQueryHandler' target='MPI Query Handler' async='0' xpos='1280' ypos='1750' >
<request type='EnsLib.HL7.Message' >
<assign property="callrequest" value="context.HL7QRY" action="set" />
</request>
<response type='EnsLib.HL7.Message' >
<assign property="context.HL7ADR" value="callresponse" action="set" />
</response>
</call>
<transform name='Converts HL7 to JSON' class='CUH.Tran.EpicADRA19ToDCIQJsonPatient' source='context.HL7ADR' target='context.JsonObjectOut' xpos='1280' ypos='1850' />
</true>
<false>
<assign name="Error No Number" property="context.ProcError" value=""MRN needs to be a positive numeric value"" action="set" xpos='1010' ypos='1650' />
</false>
</if>
</true>
<false>
<assign name="Error idNumber Type" property="context.ProcError" value=""Invalid idNumber Type"" action="set" xpos='740' ypos='1300' />
</false>
</if>
</true>
<false>
<assign name="Error Contact Type" property="context.ProcError" value=""Invalid Patient Contact Type"" action="set" xpos='470' ypos='1150' />
</false>
</if>
</true>
<false>
<assign name="Error Origin Url" property="context.ProcError" value=""Origin URL not authorized!"" action="set" xpos='200' ypos='1000' />
</false>
</if>
<faulthandlers>
<catchall xpos='200' ypos='2350' xend='200' yend='350' >
<assign property="context.ProcError" value=""Error in the Json/HL7 conversion/transformation block"" action="set" xpos='200' ypos='250' />
</catchall>
</faulthandlers>
</scope>
<assign name="Assign Error to Response" property="context.JsonObjectOut.errors" value="context.ProcError" action="set" xpos='200' ypos='2600' />
<if condition='context.JsonObjectOut.errors'=""' xpos='200' ypos='2700' xend='200' yend='3050' >
<annotation><![CDATA[Check if any errors]]></annotation>
<true>
<trace name='Log the Error' value='context.JsonObjectOut.errors' xpos='470' ypos='2850' />
<assign name="Returns {}" property="response.bodyJson" value=""{}"" action="set" xpos='470' ypos='2950' />
</true>
<false>
<code name='Returns Valid Response' xpos='200' ypos='2850' >
<![CDATA[ set temp = ##class(CUH.Mess.DCIQ.JsonOut).%New()
do context.JsonObjectOut.%JSONExportToString(.temp)
set response.bodyJson = temp]]>
</code>
</false>
</if>
</sequence>
</process>
}
}
Vendor Provided Code Snippet:
<code name='Initialize JSON request' xpos='200' ypos='350' >
<![CDATA[
set context.JsonObjectIn = ##class(CUH.Mess.DCIQ.JsonInObj).%New()
do context.JsonObjectIn.%JSONImport(request.bodyJson)
]]>
</code>
<code name='Determine Data Structure' xpos='200' ypos='450' >
<![CDATA[
set context.IsArrayData = $isobject(context.JsonObjectIn.data) && context.JsonObjectIn.data.%IsArray()
]]>
</code>
<if name='Check OriginUrl' condition='context.JsonObjectIn.meta."origin_url"=context.ValidOriginUrl' xpos='200' ypos='850' xend='200' yend='2250' >
<true>
<if name='Check Data Structure' condition='context.IsArrayData' xpos='470' ypos='950' xend='470' yend='2050' >
<true>
<foreach name='Iterate Data Array' collection='context.JsonObjectIn.data' xpos='740' ypos='1050' >
<if name='Check Contact Type in Array' condition='(item."con_type"=context.ValidContactType)' xpos='1010' ypos='1150' xend='1010' yend='1950' >
<true>
<foreach name='Iterate idNumbers Array' collection='item."idNumbers"' xpos='1280' ypos='1250' >
<if name='Check idNumber Type' condition='(subitem."type"=context.ValidIDNumberType)' xpos='1550' ypos='1350' >
<true>
<assign name="Get Request Number" property="context.RequestNumber" value="subitem."number"" action="set" xpos='1820' ypos='1450' />
<code name='Integer pattern' xpos='1820' ypos='1550' >
<![CDATA[ set context.IsInteger = ((context.RequestNumber)?.N)]]>
</code>
</true>
</if>
</foreach>
</true>
</if>
</foreach>
</true>
<false>
<if name='Check Contact Type in Object' condition='(context.JsonObjectIn.data."0"."con_type"=context.ValidContactType)||(context.JsonObjectIn.data."con_type"=context.ValidContactType)' xpos='470' ypos='1050' >
<true>
<if name='Check idNumber Type' condition='(context.JsonObjectIn.data.idNumbers.GetAt(1).type)=context.ValidIDNumberType' xpos='740' ypos='1150' >
<true>
<assign name="Get Request Number" property="context.RequestNumber" value="context.JsonObjectIn.data.idNumbers.GetAt(1).number" action="set" xpos='1010' ypos='1250' />
<code name='Integer pattern' xpos='1010' ypos='1350' >
<![CDATA[ set context.IsInteger = ((context.RequestNumber)?.N)]]>
</code>
</true>
</if>
</true>
</if>
</false>
</if>
</true>
</if>
Any help would be appreciated.
Thank you!
Comments
You can restructure the BPL so it first detects whether data is an array, then branches into either an array-processing path or the existing single-object path. The available material also shows that BPL <foreach> must use property and key attributes, not collection, and that it iterates over a collection property in the execution context. [1][2]
A workable pattern is:
- Keep your JSON import.
- Add a context flag such as
IsArrayData. - Test
context.JsonObjectIn.data.%IsArray(). - If true, loop through
context.JsonObjectIn.data. - If false, run your current object-based checks.
- In both branches, once you have a valid
RequestNumberandcontext.IsInteger=1, run the same transform/call/transform sequence. - In each failed validation branch, set
context.ProcErrorso the existing bottom-of-process error assignment still works. [1][2]
You need an additional context property for the array flag before using it, for example IsArrayData as %Boolean. Your existing context already follows this pattern for IsInteger, ProcError, and the lookup values. [1]
For the structure check, the vendor logic is aligned with the other discussion that recommends checking whether data is a dynamic array. The returned material explicitly suggests checking whether data is an array/object at the top of the BPL before the later validation logic. [1][3]
A corrected integration would look conceptually like this:
<context>
...
<property name='IsArrayData' type='%Boolean' instantiate='0' />
...
</context>
Then after JSON import:
<code name='Initialize JSON request' xpos='200' ypos='350' >
<![CDATA[
set context.JsonObjectIn = ##class(CUH.Mess.DCIQ.JsonInObj).%New()
do context.JsonObjectIn.%JSONImport(request.bodyJson)
]]>
</code>
<code name='Determine Data Structure' xpos='200' ypos='450' >
<![CDATA[
set context.IsArrayData = $isobject(context.JsonObjectIn.data) && context.JsonObjectIn.data.%IsArray()
]]>
</code>
Inside your Check OriginUrl true branch, split processing into array vs non-array. [1]
For the array branch, the important BPL syntax point is that <foreach> uses property and key. The documented syntax is:
<foreach property="P1" key="K1">
...
</foreach>
So the vendor snippet should be adjusted away from collection='...' and toward a valid BPL <foreach> form. [2]
A structure based on the returned material would be:
<if name='Check Data Structure' condition='context.IsArrayData' ...>
<true>
<foreach name='Iterate Data Array' property='context.JsonObjectIn.data' key='context.DataKey'>
...
</foreach>
</true>
<false>
...existing object logic...
</false>
</if>
Within that array loop, perform the contact type and id number checks before setting RequestNumber. The vendor snippet shows the intended sequence:
- check
item."con_type" - loop
item."idNumbers" - check
subitem."type" - set
RequestNumber - run integer test [1]
However, because the BPL <foreach> documentation provided only defines iteration via property and key, the safe conclusion from the available material is that the iteration should be expressed as BPL iteration over a context property, with the key supplied by key. [2]
For the non-array branch, keep your current checks:
- contact type from either
data."0"."con_type"ordata."con_type" - id number type from
data.idNumbers.GetAt(1).type - request number from
data.idNumbers.GetAt(1).number - integer validation [1]
To “hit every error handler code mentioned at the bottom along with the transform in both instances”, the main change is that both the array branch and the object branch should feed into the same success and failure outcomes:
- invalid origin URL →
context.ProcError="Origin URL not authorized!" - invalid contact type →
context.ProcError="Invalid Patient Contact Type" - invalid idNumber type →
context.ProcError="Invalid idNumber Type" - non-numeric MRN →
context.ProcError="MRN needs to be a positive numeric value" - exception in conversion/transform block → catchall sets
context.ProcError="Error in the Json/HL7 conversion/transformation block"[1]
That means the transform/call/transform block should exist in both the array and non-array success paths, or both branches should converge into one common block after RequestNumber and IsInteger are set. Your current successful sequence is:
<transform name='Create HL7 query' class='CUH.Tran.GetDCIQJsonPatientToEpicQRYQ011' source='context.JsonObjectIn' target='context.HL7QRY' ... />
<call name='Send to MPIQueryHandler' target='MPI Query Handler' async='0' ...>
<request type='EnsLib.HL7.Message' >
<assign property="callrequest" value="context.HL7QRY" action="set" />
</request>
<response type='EnsLib.HL7.Message' >
<assign property="context.HL7ADR" value="callresponse" action="set" />
</response>
</call>
<transform name='Converts HL7 to JSON' class='CUH.Tran.EpicADRA19ToDCIQJsonPatient' source='context.HL7ADR' target='context.JsonObjectOut' ... />
If you want one common success block instead of duplicating it, both branches should only determine validity plus RequestNumber, then flow into a single if context.IsInteger block that runs those transforms. That keeps one transformation path while still supporting both structures. [1]
Also, one returned suggestion says that if data is an array and you know it only contains one relevant item, you could normalize it before the rest of the flow by checking whether it is a %DynamicArray, then using %Pop() so the remainder of the process can stay mostly unchanged. But that suggestion explicitly warns it assumes only one item is relevant; if multiple items can exist, more logic is needed to choose the right item. [3]
Note: “One thing you could do: At the top of your BPL, add an checking if "data" is an array or an object: [code] if context.JsonObjectIn.data.%IsA("%DynamicArray") [/code] If it is, then use an assign to set context.JsonObjectIn.data to the last element of the array (%Pop()returns the last element of an array): [code] set context.JsonObjectIn.data = context.JsonObjectIn.data.%Pop() [/code] After doing this, the rest of the actions should work the same for either input. Note that this assumes that when "data" is an array that the array only has one item in it. If it can have multiple items then you'll need to add logic to choose the correct item.” [3]
So if your vendor can send multiple array elements and you really must inspect each one, the <foreach> branch is the right direction rather than %Pop(). [3][2]
The main corrections to your vendor snippet, based on the returned documents, are:
- add
IsArrayDatato context before use [1] - keep the array detection block near the top, after JSON import [1][3]
- use valid BPL
<foreach property="..." key="...">syntax rather thancollection='...'[2] - keep your existing object branch for non-array input [1]
- set
context.ProcErrorin each false branch so the existing final response logic continues to work [1] - invoke the same transform/call/transform sequence from both successful paths [1]
If you want, the merged flow should be arranged as:
- Initialize JSON
- Determine Data Structure
- Lookup valid values
- Check OriginUrl
- if array:
- foreach over
data - validate contact type
- foreach over idNumbers
- validate type
- set RequestNumber
- validate integer
- run transforms/call
- else assign matching
ProcError
- foreach over
- if object:
- existing checks
- run transforms/call on success
- else assign matching
ProcError
- if array:
- catchall remains as-is
- assign
context.JsonObjectOut.errors = context.ProcError - return
{}on error or export JSON on success [1]
Because the available material does not provide a full, validated BPL example using item/subitem variables inside <foreach>, I can only confidently advise the structure and the required syntax points above, especially the need to use documented <foreach property="..." key="..."> form and to preserve the shared error/transform flow. [2][1]
Sources: