Question
· Jan 18

Reading a XML list into a custom XML enabled class

Hello, I tried to read a XML list from a web service into a response class by creating a custom collection subclass as described in this documentation page

Here's the XML file I am trying to read:

<Suppliers>
    <Supplier>
        <row>1</row>
    </Supplier>
    <Supplier>
        <row>2</row>
    </Supplier>
</Suppliers>

And the different classes I defined to project the XML into:

Class App.Messages.GetSuppliersResponse Extends (%Persistent, Ens.Response)
{
Property Suppliers As App.Objects.Suppliers;

Method Deserialize(stream as %Stream.Object) as %Status
{
    set reader = ##class(%XML.Reader).%New()
    $$$ThrowOnError(reader.OpenStream(.stream))
    Do reader.CorrelateRoot($CLASSNAME(..Suppliers))
    Do reader.Next(..Suppliers, .tSC)
    quit tSC
}
}

Class App.Objects.Suppliers Extends (%ListOfObjects, %XML.Adaptor)
{
Parameter XMLNAME = "Suppliers";
Parameter XMLITEMNAME = "Supplier";
Parameter ELEMENTTYPE = "App.Objects.Supplier";
}

Class App.Objects.Supplier Extends (%Persistent, %XML.Adaptor)
{
Parameter XMLNAME = "Supplier";

Property row As %Integer(XMLNAME = "row");
}

I just cannot figure out how to read a XML where the root is a list, and get the following error by calling the method Deserialize in the response class:

ERROR #6237: Unexpected tag in XML input: Supplier (ending at line 2 character 15).

Has someone ever dealt with this kind of problem?

Any help would be appreciated, thank you

Product version: IRIS 2023.3
Discussion (3)2
Log in or sign up to continue

First I'd suggest not to use a persistent class "linked" to a message (Ens.Response in this case), Supplier  is linked by your message class in this case.
If you do it, you will definitely create "orphaned" persistent objects when you purge, unless you add some logic (like triggers and callbacks) to delete the "linked" persistent objects when a message is purged.

To avoid this (when possible) a serial class is preferred.

So, the Supplier class would be:

Class Community.App.Msg.Supplier Extends (%SerialObject, %XML.Adaptor)
{
Property row As %Integer;
}

As for the message class:

Class Community.App.Msg.Suppliers Extends Ens.Response
{

Property Supplier As list Of Community.App.Msg.Supplier(XMLPROJECTION = "ELEMENT");
ClassMethod test() As %Status
{
    set XmlString="<Suppliers><Supplier><row>1</row></Supplier><Supplier><row>2</row></Supplier></Suppliers>"
    set reader = ##class(%XML.Reader).%New()
    $$$ThrowOnError(reader.OpenString(XmlString))
    do reader.CorrelateRoot("Community.App.Msg.Suppliers")
    do reader.Next(.Suppliers, .tSC)
    do Suppliers.XMLExport(,",indent")
    quit tSC
}
}

For simplicity I modified the sample to be a classmethod.
When the test() method is run the output is:

EPTEST>d ##class(Community.App.Msg.Suppliers).test()
<Suppliers>
  <Supplier>
    <row>1</row>
  </Supplier>
  <Supplier>
    <row>2</row>
  </Supplier>
</Suppliers>

The relevant documentation is "Controlling the Form of the Projection for Collection Properties" here.

Let me add a few notes.

If a class extend Ens.Response or Ens.Request  is not necessary to extend %Persistent because is already inherited by Ens.Request/Ens.Response.
Edit: please see Antoine commnet below

If the desired XML tag correspond to the class name (without package), it is not necessary to add the XMLNAME parameter, like you did in App.Objects.Suppliers class.

Same goes for XMLNAME property parameter XMLNAME = "row" is not necessary because the property name correspond to the desired XML tag name.

Oh I see, what I was really missing out there was the XMLPROJECTION = "ELEMENT"

Thank you very much for your answer and clearing out some misconceptions about persistent classes

Though I have one additional note here:

If a class extend Ens.Response or Ens.Request  is not necessary to extend %Persistent because is already inherited by Ens.Request/Ens.Response.

From what I have read in the IRIS documentation, it is advised to also extend a message class with %Persistent as the primary subclass for performance reasons

see https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=EGDV_messages#EGDV_message_body_class

If Ens.Request or Ens.Response
is the primary superclass, the messages will be saved in the same table that stores all other requests or responses, and this can adversely affect performance when querying the message tables.