Question
· Jun 29, 2022

Help with JSON/XDATA Mapping

Hi There,

I'm fairly new to ObjectScript/Ensemble and I'm sure I may be fundamentally misunderstanding how the %JSONImport/%JSONExport and XDATA mapping work and I was hoping you good folks could help me out.  I'm essentially trying to create an object that I can use to translate from one JSON schema to another. 

First my class:

Class SERVICESVCPKG.Model.AthenaAppointment Extends (%Persistent, %DynamicAbstractObject, %JSON.Adaptor)
{

Parameter %JSONENABLED = 1;

Property AppointmentID As %String(%JSONFIELDNAME = "AppointmentID", %JSONINCLUDE = "inout");

Property AppointmentType As %String(%JSONINCLUDE = "inout");

Property AppointmentTypeID As %String(%JSONINCLUDE = "inout");

Property Date As %String(%JSONINCLUDE = "inout");

Property DepartmentID As %String(%JSONINCLUDE = "inout");

Property Duration As %Integer(%JSONINCLUDE = "inout");

Property PatientAppointmentTypeName As %String(%JSONINCLUDE = "inout");

Property LocalProviderID As %String(%JSONINCLUDE = "inout");

Property ProviderID As %String(%JSONINCLUDE = "inout");

Property StartTime As %String(%JSONINCLUDE = "inout");

Property Reason As %String(%JSONINCLUDE = "inout");

XData AthenaAppointment
{
<Mapping xmlns="http://www.intersystems.com/jsonmapping">
        <Property Name="AppointmentID" FieldName="appointmentid" />
        <Property Name="AppointmentType" FieldName="appointmenttype" />
        <Property Name="AppointmentTypeID" FieldName="appointmenttypeid" />
        <Property Name="Date" FieldName="date" />
        <Property Name="DepartmentID" FieldName="departmentid" />
        <Property Name="Duration" FieldName="duration" />
        <Property Name="PatientAppointmentTypeName" FieldName="patientappointmenttypename" />
        <Property Name="LocalProviderID" FieldName="localproviderid" />
        <Property Name="ProviderID" FieldName="providerid" />
        <Property Name="StartTime" FieldName="starttime" />
        <Property Name="Reason" FieldName="reason" />
</Mapping>
}

Storage Default
{
<Data name="AthenaAppointmentDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>AppointmentID</Value>
</Value>
<Value name="3">
<Value>AppointmentType</Value>
</Value>
<Value name="4">
<Value>AppointmnetTypeID</Value>
</Value>
<Value name="5">
<Value>Date</Value>
</Value>
<Value name="6">
<Value>DepartmentID</Value>
</Value>
<Value name="7">
<Value>Duration</Value>
</Value>
<Value name="8">
<Value>PatientAppointmentTypeName</Value>
</Value>
<Value name="9">
<Value>LocalProviderID</Value>
</Value>
<Value name="10">
<Value>ProviderID</Value>
</Value>
<Value name="11">
<Value>StartTime</Value>
</Value>
<Value name="12">
<Value>Reason</Value>
</Value>
<Value name="13">
<Value>AppointmentTypeID</Value>
</Value>
</Data>
<DataLocation>^SERVICESVC2A71.AthenaAppoi8EB1D</DataLocation>
<DefaultData>AthenaAppointmentDefaultData</DefaultData>
<IdLocation>^SERVICESVC2A71.AthenaAppoi8EB1D</IdLocation>
<IndexLocation>^SERVICESVC2A71.AthenaAppoi8EB1I</IndexLocation>
<StreamLocation>^SERVICESVC2A71.AthenaAppoi8EB1S</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}

Here's what I see what I try to implement that class and call the JSON methods. Nothing seems to be translating into my AthenaAppointment Object.

^XVMC(0)="{""date"":""06/27/2022"",""appointmentid"":1214525,""departmentid"":195,""localproviderid"":187,""appointmenttype"":""NEW PATIENT 45"",""providerid"":187,""starttime"":""14:00"",""duration"":45,""appointmenttypeid"":1188,""reasonid"":[""-1""],""patientappointmenttypename"":""New Patient""}
SERVICESVC>set myObject = ##class(%DynamicAbstractObject).%FromJSON(^XVMC(0))

SERVICESVC>zw myObject
myObject={"date":"06/27/2022","appointmentid":1214525,"departmentid":195,"localproviderid":187,"appointmenttype":"NEW PATIENT 45","providerid":187,"starttime":"14:00","duration":45,"appointmenttypeid":1188,"reasonid":["-1"],"patientappointmenttypename":"New Patient"}  ; <DYNAMIC OBJECT>


d AthenaAppointment.%JSONImport(myObject,"AthenaAppointment")

SERVICESVC>zw AthenaAppointment
AthenaAppointment=3@SERVICESVCPKG.Model.AthenaAppointment  ; <OREF>
+----------------- general information ---------------
|      oref value: 3
|      class name: SERVICESVCPKG.Model.AthenaAppointment
| reference count: 2
+----------------- attribute values ------------------
|       %Concurrency = 1  <Set>
|      AppointmentID = ""
|    AppointmentType = ""
|  AppointmentTypeID = ""
|               Date = ""
|       DepartmentID = ""
|           Duration = ""
|    LocalProviderID = ""
|PatientAppointmentTypeName = ""
|         ProviderID = ""
|             Reason = ""
|          StartTime = ""
+-----------------------------------------------------

Any nudges in the right direction would be greatly appreciated!

Product version: IRIS 2022.1
$ZV: IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2021.2 (Build 651U) Mon Jan 31 2022 18:07:01 EST
Discussion (3)1
Log in or sign up to continue

Method %JSONImport returns a %Status, which tells you what the problem is (using $System.Status.DisplayError()):

ERROR #9406: Unexpected format for value of field, appointmentid, using AthenaAppointment mapping

That value is a number in JSON, but you defined it as a %String in the class. The JSON import code disapproves. There are more fields like that, and additionally field reasonid is not defined in the mapping. If you fix these problems, the data will import.

(I'd also remove the %DynamicAbstractObject superclass, it is unneeded and gives errors on object destruction.)

Class dc.test Extends (%RegisteredObject%JSON.Adaptor)
{

Parameter %JSONENABLED = 1;

Property AppointmentID As %String(%JSONFIELDNAME "AppointmentID"%JSONINCLUDE "inout");
Property AppointmentType As %String(%JSONINCLUDE "inout");
Property AppointmentTypeID As %String(%JSONINCLUDE "inout");
Property Date As %String(%JSONINCLUDE "inout");
Property DepartmentID As %String(%JSONINCLUDE "inout");
Property Duration As %Integer(%JSONINCLUDE "inout");
Property PatientAppointmentTypeName As %String(%JSONINCLUDE "inout");
Property LocalProviderID As %String(%JSONINCLUDE "inout");
Property ProviderID As %String(%JSONINCLUDE "inout");
Property StartTime As %String(%JSONINCLUDE "inout");
Property Reason As %String(%JSONINCLUDE "inout");

XData AthenaAppointment
{
<Mapping xmlns="http://www.intersystems.com/jsonmapping">
  <Property Name="AppointmentID" FieldName="appointmentid" />
  <Property Name="AppointmentType" FieldName="appointmenttype" />
  <Property Name="AppointmentTypeID" FieldName="appointmenttypeid" />
  <Property Name="Date" FieldName="date" />
  <Property Name="DepartmentID" FieldName="departmentid" />
  <Property Name="Duration" FieldName="duration" />
  <Property Name="PatientAppointmentTypeName" FieldName="patientappointmenttypename" />
  <Property Name="LocalProviderID" FieldName="localproviderid" />
  <Property Name="ProviderID" FieldName="providerid" />
  <Property Name="StartTime" FieldName="starttime" />
  <Property Name="Reason" FieldName="reasonid" />
</Mapping>
}

/// d ##class(dc.test).Test()
ClassMethod Test()
{
  json="{""date"":""06/27/2022"",""appointmentid"":""1214525"",""departmentid"":""195"",""localproviderid"":""187"",""appointmenttype"":""NEW PATIENT 45"",""providerid"":""187"",""starttime"":""14:00"",""duration"":45,""appointmenttypeid"":""1188"",""reasonid"":""-1"",""patientappointmenttypename"":""New Patient""}"
  
  try{

    tmp=..%New()
    
    $$$ThrowOnError(tmp.%JSONImport(json,"AthenaAppointment"))

    $$$ThrowOnError($system.OBJ.Dump(tmp))
  
  }catch(ex){
    #dim ex As %Exception.AbstractException
    ex.DisplayString()
  }
}

}

Output:

USER>##class(dc.test).Test()
+----------------- general information ---------------
|      oref value: 3
|      class name: dc.test
| reference count: 1
+----------------- attribute values ------------------
|      AppointmentID = 1214525
|    AppointmentType = "NEW PATIENT 45"
|  AppointmentTypeID = 1188
|               Date = "06/27/2022"
|       DepartmentID = 195
|           Duration = 45
|    LocalProviderID = 187
|PatientAppointmentTypeName = "New Patient"
|         ProviderID = 187
|             Reason = -1
|          StartTime = "14:00"
+-----------------------------------------------------

PS: pay special attention to the reason field: is it a string or an array of strings?

Thank you both so much! Now to figure out how to do this with embedded objects. For my destination objects. 

I'd imagine I'd define in my parent class

Property ExternalAlias As SERVICESVCPKG.Model.IntSvc.ExternalAlias(%JSONFIELDNAME = "ExternalAlias", %JSONINCLUDE = "inout");
Class SERVICESVCPKG.Model.IntSvc.ExternalAlias Extends (%SerialObject, %JSON.Adaptor)
{

Parameter %JSONENABLED = 1;

Property TenantID As %String(%JSONFIELDNAME = "TenantID", %JSONINCLUDE = "inout");

Property SystemID As %String(%JSONFIELDNAME = "SystemID", %JSONINCLUDE = "inout");

Property ExternalType As %String(%JSONFIELDNAME = "ExternalType", %JSONINCLUDE = "inout");

Property ExternalID As %String(%JSONFIELDNAME = "ExternalID", %JSONINCLUDE = "inout");

}

Thank you all again, I swear I've been banging my head against this for like a week with 10 tabs of docs open.