XML string to Serial Object

Hello.

I want to know how can I convert XML String intro a %SerialObject.

This is an example:

<Envelope>
    <Body>
        <RESULT>
            <SUCCESS>TRUE</SUCCESS>
            <LIST>
                <ID>11111</ID>
                <NAME>one</NAME>
            </LIST>
            <LIST>
                <ID>22222</ID>
                <NAME>two</NAME>
            </LIST>
        </RESULT>
    </Body>
</Envelope>

And this are the %SerialObject classes I have created:

Class test.Envelope Extends (%SerialObject,%XML.Adaptor)
{
Property Body As test.Body;
}
Class test.Body Extends (%SerialObject,%XML.Adaptor)
{
Property RESULT As test.Result;
}
Class test.Result Extends (%SerialObject,%XML.Adaptor)
{
Property SUCCESS As %String(MAXLEN = "");
Property LIST As list Of test.List;
}
Class test.List Extends (%SerialObject,%XML.Adaptor)
{
Property ID As %String(MAXLEN = "");
Property NAME As %String(MAXLEN = "");
}

I have tested with a function like this:

set reader = ##class(%XML.Reader).%New()
set sc = reader.OpenStream(xmlStream)
do reader.Rewind()
do reader.CorrelateRoot("test.Envelope")
while (reader.Next(.tMessage,.sc)) {
    set OUT = tMessage
}

But it only works if property "LIST" is a single object and not a list of objects. When I try with the list, I get this error:

ERROR #6237: Unexpected tag in XML input: LIST (ending at line 6 character 5).

How can I convert a XML String into a SerialObject?

Thank you in advance.

Answers

Hi Laura,

I find it simpler to write an XSD (even if you don't have one) and then use the XML code generator wizard.

A good place to start is by using an online tool that will auto generate an XSD from XML for you, such at the one you can find here...

https://www.liquid-technologies.com/online-xml-to-xsd-converter

Sometimes you will need to massage the XML a little first, for instance add a second instance of an element when you only have one in your example and you know there will be many.

So you would end up with an XSD that looks like this...

<?xml version="1.0" encoding="utf-8"?>
<!-- Created with Liquid Technologies Online Tools 1.0 (https://www.liquid-technologies.com) -->
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Envelope">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Body">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="RESULT">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="SUCCESS" type="xs:string" />
                    <xs:element maxOccurs="unbounded" name="LIST">
                      <xs:complexType>
                        <xs:sequence>
                          <xs:element name="ID" type="xs:unsignedShort" />
                          <xs:element name="NAME" type="xs:string" />
                        </xs:sequence>
                      </xs:complexType>
                    </xs:element>
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Save this to a file and from studio select "Tools" from the main menu, then "add-ins" and then "XML Schema Wizard" which might be visible or you might need to click "add-ins" again to bring it into focus.

Select the file, click next, de-select the "Create Persistent Classes" option and type in a package name, click next and select "Serial" for each of the classes, click next and the classes will be generated. You might need to go and compile them before they can be used. The generated code looks like this...

Class Spuds.Envelope Extends (%SerialObject, %XML.Adaptor) [ ProcedureBlock ]
{

Parameter XMLNAME = "Envelope";

Parameter XMLSEQUENCE = 1;

Property Body As Spuds.RESULT(XMLNAME = "Body", XMLPROJECTION = "WRAPPED") [ Required ];

}
Class Spuds.RESULT Extends (%SerialObject, %XML.Adaptor) [ ProcedureBlock ]
{

Parameter XMLNAME = "RESULT";

Parameter XMLSEQUENCE = 1;

Property SUCCESS As %String(MAXLEN = "", XMLNAME = "SUCCESS") [ Required ];

Property LIST As list Of Spuds.LIST(XMLNAME = "LIST", XMLPROJECTION = "ELEMENT") [ Required ];

}
Class Spuds.LIST Extends (%SerialObject, %XML.Adaptor) [ ProcedureBlock ]
{

Parameter XMLNAME = "LIST";

Parameter XMLSEQUENCE = 1;

Property ID As %xsd.unsignedShort(XMLNAME = "ID") [ Required ];

Property NAME As %String(MAXLEN = "", XMLNAME = "NAME") [ Required ];

}

And here is the solution in action...

TEST>s xml="<Envelope><Body><RESULT><SUCCESS>TRUE</SUCCESS><LIST><ID>11111</ID><NAME>one</NAME></LIST><LIST><ID>22222</ID><NAME>two</NAME></LIST></RESULT></Body></Envelope>"
 
TEST>set reader=##class(%XML.Reader).%New()                                      

TEST>set sc=reader.OpenString(xml)                                               

TEST>do reader.Correlate("Envelope","Spuds.Envelope")                            

TEST>do reader.Next(.envelope,.sc)                                               
 
TEST>w envelope.Body.SUCCESS
TRUE

TEST>w envelope.Body.LIST.GetAt(1).ID
11111

TEST>w envelope.Body.LIST.GetAt(1).NAME
one

Thank you very much! I have tested this, and it's easier to create the full structure with this. This is very useful to us

Default XMLPROJECTION for collection properties is WRAPPED, which adds wrapping tag.

Define your list property this way:

Property LIST As list Of test.List(XMLPROJECTION = "ELEMENT");

Comments

Laura Blázquez you can do that with JSON because JSON is schema less but XML objects need a schema in a explicit or implicit way you need to define how the object is represented by the XML notation and viceversa.