Question
· Aug 9, 2023

How do I convert my JSON Response to an Ens.Response object with Lists of Arrays

Still working on my first External REST API call, and I am struggling to find the exact answer I am looking for... I get a JSON response from my API call but I am not quite sure how to dynamically get the JSON response into the Ens.Response Object with its lists of Arrays that I have defined.

  set tSC = ..Adapter.SendFormDataArray(.tHTTPResposne,"POST",tHTTPRequest,,,tURL)

  set pResponse = ##class(User.REST.Epic.Msg.GetPatientLocationResponse).%New()

  set dynObject = {}.%FromJSON(tHTTPResposne.Data)

  set iter = dynObject.%GetIterator()

  while iter.%GetNext(.key,.value){

    $$$TRACE("key = "_key_", value = "_value)

}

While the code above will display me the values, does anyone have a dynamic way to read in the JSON response into the Ens.Response object that I have created?

Here is my Ens.Response definition...

Class User.REST.Epic.Msg.GetPatientLocationResponse Extends (Ens.Response, %JSON.Adaptor)

{

Property Results As list Of User.REST.Epic.dt.Response;

}

 

Class User.REST.Epic.dt.Response Extends (%SerialObject, %XML.Adaptor, %JSON.Adaptor)

{

 

Property AppointmentSchedules As User.REST.Epic.dt.ArrayOfScheduleProviderReturn(%JSONFIELDNAME = "AppointmentSchedules");

 

Property AttendingPhysicians As User.REST.Epic.dt.ArrayOfAttendingPhysician(%JSONFIELDNAME = "AttendingPhysicians");

 

Property CareTeamPCPs As User.REST.Epic.dt.ArrayOfCareTeamPCP(%JSONFIELDNAME = "CareTeamPCPs");

 

Property ChargeSlipNumber As %String(%JSONFIELDNAME = "ChargeSlipNumber", MAXLEN = "");

 

Property DateOfBirth As %String(%JSONFIELDNAME = "DateOfBirth", MAXLEN = "");

 

Property DepartmentIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "DepartmentIDs");

 

Property EncounterDate As %String(%JSONFIELDNAME = "EncounterDate", MAXLEN = "");

 

Property FacilityIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "FacilityIDs");

 

Property FirstName As %String(%JSONFIELDNAME = "FirstName", MAXLEN = "");

 

Property HospitalAccountIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "HospitalAccountIDs");

 

Property HospitalService As %String(%JSONFIELDNAME = "HospitalService", MAXLEN = "");

 

Property LastName As %String(%JSONFIELDNAME = "LastName", MAXLEN = "");

 

Property MiddleName As %String(%JSONFIELDNAME = "MiddleName", MAXLEN = "");

 

Property PatientClass As %String(%JSONFIELDNAME = "PatientClass", MAXLEN = "");

 

Property PatientPhoneNumbers As User.REST.Epic.dt.ArrayOfPhone(%JSONFIELDNAME = "PatientPhoneNumbers");

 

Property ProviderTeams As User.REST.Epic.dt.ArrayOfProviderTeam(%JSONFIELDNAME = "ProviderTeams");

 

Property Sex As %String(%JSONFIELDNAME = "Sex", MAXLEN = "");

}

Thanks

Scott

Product version: IRIS 2023.1
Discussion (10)2
Log in or sign up to continue

@Ashok Kumar 
So, I tried...

 set tSC = ..Adapter.SendFormDataArray(.tHTTPResposne,"POST",tHTTPRequest,,,tURL)
 set pResponse = ##class(User.REST.Epic.Msg.GetPatientLocationResponse).%New()
 set dynObject = {}.%FromJSON(tHTTPResposne.Data)
 do pResponse.%JSONImport(dynObject)
 
and tried..
set tSC = ..Adapter.SendFormDataArray(.tHTTPResposne,"POST",tHTTPRequest,,,tURL)
set pResponse = ##class(User.REST.Epic.Msg.GetPatientLocationResponse).%New()
do pResponse.%JSONImport(tHTTPResposne.Data)
But within the trace viewer GetPatientLocationResponse comes up blank

Hey Scott; I haven't read through all of this but use

Set tSC = pResponse.%JSONImport(tHTTPResponse.Data)
Quit:$$$ISERR(tSC) tSC

So you can capture perhaps why the import is failing.

Generally speaking as well, for items like:

Property AppointmentSchedules As User.REST.Epic.dt.ArrayOfScheduleProviderReturn(%JSONFIELDNAME = "AppointmentSchedules");

Which I believe is referencing a JSON Array of objects, you would need to do the following at bare minimum;

Property AppointmentSchedules As list Of User.REST.Epic.dt.ArrayOfScheduleProviderReturn(%JSONFIELDNAME = "AppointmentSchedules");
 

Believe you have several of these situations.

Thanks for the suggestion. 

I was able to find the following error...

ERROR #9404: Unexpected field in input, LastName, using class base mapping.

It shouldn't matter the order in which the Object Class was defined would it?

as my Base class references another Class

class User.REST.Epic.Msg.GetPatientLocationResponse Extends (Ens.Response, %JSON.Adaptor)
{
Property Results As list Of User.REST.Epic.dt.Response;
}
Class User.REST.Epic.dt.Response Extends (%SerialObject, %XML.Adaptor, %JSON.Adaptor)
{
Property AppointmentSchedules As User.REST.Epic.dt.ArrayOfScheduleProviderReturn(%JSONFIELDNAME = "AppointmentSchedules");
Property AttendingPhysicians As User.REST.Epic.dt.ArrayOfAttendingPhysician(%JSONFIELDNAME = "AttendingPhysicians");
Property CareTeamPCPs As User.REST.Epic.dt.ArrayOfCareTeamPCP(%JSONFIELDNAME = "CareTeamPCPs");
Property ChargeSlipNumber As %String(%JSONFIELDNAME = "ChargeSlipNumber", MAXLEN = "");
Property DateOfBirth As %String(%JSONFIELDNAME = "DateOfBirth", MAXLEN = "");
Property DepartmentIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "DepartmentIDs");
Property EncounterDate As %String(%JSONFIELDNAME = "EncounterDate", MAXLEN = "");
Property FacilityIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "FacilityIDs");
Property FirstName As %String(%JSONFIELDNAME = "FirstName", MAXLEN = "");
Property HospitalAccountIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "HospitalAccountIDs");
Property HospitalService As %String(%JSONFIELDNAME = "HospitalService", MAXLEN = "");
Property LastName As %String(%JSONFIELDNAME = "LastName", MAXLEN = "");
Property MiddleName As %String(%JSONFIELDNAME = "MiddleName", MAXLEN = "");
Property PatientClass As %String(%JSONFIELDNAME = "PatientClass", MAXLEN = "");
Property PatientPhoneNumbers As User.REST.Epic.dt.ArrayOfPhone(%JSONFIELDNAME = "PatientPhoneNumbers");
Property ProviderTeams As User.REST.Epic.dt.ArrayOfProviderTeam(%JSONFIELDNAME = "ProviderTeams");
Property Sex As %String(%JSONFIELDNAME = "Sex", MAXLEN = "");
}

and in User.REST.Epic.dt.Response definitely does not have LastName listed first, but in the JSON result I am seeing it listed as the first Property.

{"LastName":.......}

If you're receiving unexpected fields(key value pairs) as part of the JSON and the properties are not included in the class definition. You need to add the below parameter in your class definition(%JSON.Adaptor extended class). This will ignore loading the unexpected field.

Parameter %JSONIGNOREINVALIDFIELD As BOOLEAN = 1

In addition, The JSON key-value pair data type should match with class definition property datatype. 

I updated my User.REST.Epic.Msg.GetPatientLocationResponse that points to User.REST.Epic.dt.Response with the order in which fields are being returned in the JSON that I saw using postman. However now I am receiving...

ERROR #9404: Unexpected field in input, LastName, using class base mapping.

Method GetPatientLocationVisit2(pRequest As User.REST.Epic.Msg.GetPatientLocationRequest, Output pResponse As User.REST.Epic.Msg.GetPatientLocationResponse) As %Status

{
  #dim tSC As %Status = $$$OK
  set tHTTPRequest = ##class(%Net.HttpRequest).%New()
  set tHTTPRequest.SSLConfiguration = ..Adapter.SSLConfig
  set tHTTPRequest.Https = 1
  set tHTTPRequest.WriteRawMode = 1
  set tHTTPRequest.Port = ..Adapter.HTTPPort
  //Do tHTTPRequest.SetHeader("Authorization",..Adapter.Credentials)
  Do tHTTPRequest.SetHeader("Host",..Adapter.HTTPServer)
  Do tHTTPRequest.SetHeader("Accept-Encoding","application/json")
  Do tHTTPRequest.SetHeader("Content-Type","application/json")
  Do tHTTPRequest.SetHeader("Epic-Client-ID",..EpicClientID)
  Do tHTTPRequest.EntityBody.Write()
  do tHTTPRequest.OutputHeaders()
  set tRequest = ##class(%DynamicObject).%New()
  set tRequest.PatientID = pRequest.PatientID
  set tRequest.PatientIDType = pRequest.PatientIDType
  set tRequest.ContactID = pRequest.ContactID
  set tRequest.ContactIDType = pRequest.ContactIDType
  set tRequest.UserID = pRequest.UserID
  set tRequest.UserIDType = pRequest.UserIDType
  set tPayload = tRequest.%ToJSON()

  set tURL= ..Adapter.URL_"/2014/Access/Patient/GETPATIENTLOCATIONBYVISIT2/Visit/Location2"  //..Adapter.URL
  SET tSC = tHTTPRequest.EntityBody.Write(tPayload)
  set tHTTPResponse = ##class(%Net.HttpResponse).%New()
  set tSC = ..Adapter.SendFormDataArray(.tHTTPResponse,"POST",tHTTPRequest,,,tURL)
  set pResponse = ##class(User.REST.Epic.Msg.GetPatientLocationResponse).%New()
  set tSC = pResponse.%JSONImport(tHTTPResponse.Data)
  quit tSC
}
Class User.REST.Epic.Msg.GetPatientLocationResponse Extends (Ens.Response, %JSON.Adaptor)
{
Property Results As list Of User.REST.Epic.dt.Response;
Storage Default
{
<Data name="GetPatientLocationResponseDefaultData">
<Subscript>"GetPatientLocationResponse"</Subscript>
<Value name="1">
<Value>Results</Value>
</Value>
</Data>
<DefaultData>GetPatientLocationResponseDefaultData</DefaultData>
<Type>%Storage.Persistent</Type>
}
}
Class User.REST.Epic.dt.Response Extends (%SerialObject, %XML.Adaptor, %JSON.Adaptor)
{

Parameter %JSONIGNOREINVALIDFIELD As BOOLEAN = 1;
Property LastName As %String(%JSONFIELDNAME = "LastName", MAXLEN = "");
Property FirstName As %String(%JSONFIELDNAME = "FirstName", MAXLEN = "");
Property MiddleName As %String(%JSONFIELDNAME = "MiddleName", MAXLEN = "");
Property Sex As %String(%JSONFIELDNAME = "Sex", MAXLEN = "");
Property DateOfBirth As %String(%JSONFIELDNAME = "DateOfBirth", MAXLEN = "");
Property PatientClass As %String(%JSONFIELDNAME = "PatientClass", MAXLEN = "");
Property EncounterDate As %String(%JSONFIELDNAME = "EncounterDate", MAXLEN = "");
Property HospitalService As %String(%JSONFIELDNAME = "HospitalService", MAXLEN = "");
Property ChargeSlipNumber As list Of %String(%JSONFIELDNAME = "ChargeSlipNumber", MAXLEN = "");
Property DepartmentIDs As list Of User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "DepartmentIDs");
Property FacilityIDs As list Of User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "FacilityIDs");
Property HospitalAccountIDs As list Of User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "HospitalAccountIDs");
Property PatientPhoneNumbers As list Of User.REST.Epic.dt.ArrayOfPhone(%JSONFIELDNAME = "PatientPhoneNumbers");
Property AttendingPhysicians As list Of User.REST.Epic.dt.ArrayOfAttendingPhysician(%JSONFIELDNAME = "AttendingPhysicians");
Property ProviderTeams As list Of User.REST.Epic.dt.ArrayOfProviderTeam(%JSONFIELDNAME = "ProviderTeams");
Property CareTeamPCPs As list Of User.REST.Epic.dt.ArrayOfCareTeamPCP(%JSONFIELDNAME = "CareTeamPCPs");
Property AppointmentSchedules As list Of User.REST.Epic.dt.ArrayOfScheduleProviderReturn(%JSONFIELDNAME = "AppointmentSchedules");
Storage Default
{
<Data name="ResponseState">
<Value name="1">
<Value>LastName</Value>
</Value>
<Value name="2">
<Value>FirstName</Value>
</Value>
<Value name="3">
<Value>MiddleName</Value>
</Value>
<Value name="4">
<Value>Sex</Value>
</Value>
<Value name="5">
<Value>DateOfBirth</Value>
</Value>
<Value name="6">
<Value>PatientClass</Value>
</Value>
<Value name="7">
<Value>EncounterDate</Value>
</Value>
<Value name="8">
<Value>HospitalService</Value>
</Value>
<Value name="9">
<Value>ChargeSlipNumber</Value>
</Value>
<Value name="10">
<Value>DepartmentIDs</Value>
</Value>
<Value name="11">
<Value>FacilityIDs</Value>
</Value>
<Value name="12">
<Value>HospitalAccountIDs</Value>
</Value>
<Value name="13">
<Value>PatientPhoneNumbers</Value>
</Value>
<Value name="14">
<Value>AttendingPhysicians</Value>
</Value>
<Value name="15">
<Value>ProviderTeams</Value>
</Value>
<Value name="16">
<Value>CareTeamPCPs</Value>
</Value>
<Value name="17">
<Value>AppointmentSchedules</Value>
</Value>
</Data>
<State>ResponseState</State>
<StreamLocation>^User.REST.Epic.dt.ResponseS</StreamLocation>
<Type>%Storage.Serial</Type>
}

}

Hey Scott - While the order itself isn't important as long as your %JSONFIELDNAME values are proper, my guess is that the current error you are hitting is one of the sub-class definitions we can't see here is missing this as well:

Parameter %JSONIGNOREINVALIDFIELD As BOOLEAN = 1;

For instance, I bet LastName is a field coming back in the object represented by this property

Property AttendingPhysicians As list Of User.REST.Epic.dt.ArrayOfAttendingPhysician(%JSONFIELDNAME = "AttendingPhysicians");

So you would have to add that class param to that class as well (or wherever appropriate downstream) unless you intend to handle the LastName field appropriately.

Hello Scott, 

You have lot of object properties declared in your class definition. So, You should initialize the parameter to handle the unexpected fields like extending the %JSON.Adaptor in your class definitions. Otherwise it throw an error. So, Make sure the %JSONFIELDNAME was added if the JSON field name is different from your property and add parameter  %JSONIGNOREINVALIDFIELD to avoid unexpected field while loading JSON into object. Here is the article  about  the JSON adaptor

Parameter %JSONIGNOREINVALIDFIELD As BOOLEAN = 1

Yes, You can order your JSON structure by rearranging the property back and forth.

I rewrote my Response class, however now it is returning that it is successful, but does not display it in the trace viewer. Am I missing something in my Response class?

Class User.REST.Epic.Msg.GetPatientLocationResponse Extends (%Persistent, Ens.Response, %JSON.Adaptor, %XML.Adaptor)

{

Parameter %JSONIGNOREINVALIDFIELD As BOOLEAN = 1;
Parameter XMLNAME = "Patient.GetPatientLocationByVisit2";
Parameter XMLSEQUENCE = 1;
Parameter XMLTYPE = "Patient.GetPatientLocationByVisit2";
Property LastName As %String(%JSONFIELDNAME = "LastName", XMLNAME = "LastName");
Property FirstName As %String(%JSONFIELDNAME = "FirstName", XMLNAME = "FirstName");
Property MiddleName As %String(%JSONFIELDNAME = "MiddleName");
Property Sex As %String(%JSONFIELDNAME = "Sex");
Property DateOfBirth As %String(%JSONFIELDNAME = "DateOfBirth");
Property PatientClass As %String(%JSONFIELDNAME = "PatientClass");
Property EncounterDate As %String(%JSONFIELDNAME = "EncounterDate");
Property ChargeSlipNumber As %String(%JSONFIELDNAME = "ChargeSlipNumber");
Property DepartmentIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "DepartmentIDs", %JSONINCLUDE = "INPUTONLY");
Property FacilityIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "FacilityIDs", %JSONINCLUDE = "INPUTONLY");
Property HospitalAccountIDs As User.REST.Epic.dt.ArrayOfIDType(%JSONFIELDNAME = "HospitalAccountIDs", %JSONINCLUDE = "INPUTONLY");
Property PatientPhoneNumbers As User.REST.Epic.dt.ArrayOfPhone(%JSONFIELDNAME = "PatientPhoneNumbers", %JSONINCLUDE = "INPUTONLY");
Property AttendingPhysician As User.REST.Epic.dt.ArrayOfAttendingPhysician(%JSONINCLUDE = "INPUTONLY");
Property CareTeamPCPs As User.REST.Epic.dt.ArrayOfCareTeamPCP(%JSONINCLUDE = "INPUTONLY");
Property AppointmentSchedules As User.REST.Epic.dt.ArrayOfScheduleProviderReturn(%JSONFIELDNAME = "AppointmentSchedules", %JSONINCLUDE = "INPUTONLY");
Storage Default
{
<Data name="GetPatientLocationResponseDefaultData">
<Subscript>"GetPatientLocationResponse"</Subscript>
<Value name="1">
<Value>LastName</Value>
</Value>
<Value name="2">
<Value>FirstName</Value>
</Value>
<Value name="3">
<Value>MiddleName</Value>
</Value>
<Value name="4">
<Value>Sex</Value>
</Value>
<Value name="5">
<Value>DateOfBirth</Value>
</Value>
<Value name="6">
<Value>PatientClass</Value>
</Value>
<Value name="7">
<Value>EncounterDate</Value>
</Value>
<Value name="8">
<Value>ChargeSlipNumber</Value>
</Value>
<Value name="9">
<Value>DepartmentIDs</Value>
</Value>
<Value name="10">
<Value>FacilityIDs</Value>
</Value>
<Value name="11">
<Value>HospitalAccountIDs</Value>
</Value>
<Value name="12">
<Value>PatientPhoneNumbers</Value>
</Value>
<Value name="13">
<Value>AttendingPhysician</Value>
</Value>
<Value name="14">
<Value>CareTeamPCPs</Value>
</Value>
<Value name="15">
<Value>AppointmentSchedules</Value>
</Value>
</Data>
<DefaultData>GetPatientLocationResponseDefaultData</DefaultData>
<Type>%Storage.Persistent</Type>
}

}

Just a quick stab but I believe it's because you're extending %Persistent first and not Ens.Response and not specifying inheritance from the right so it's not appropriately applying the message viewer projection.

You normally wouldn't need to extend %persistent if these are going to be transient objects that you want to purge out via normal Ensemble purge (be sure to add in the appropriate logic to cascade delete child objects that are not %serial. Lots of posts on this "cascade delete"