Written by

Senior developer at Cysnet
Question Laura Blázquez García · May 16, 2018

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.

Comments

David Reche  May 16, 2018 to Laura Blázquez García

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.

0
Laura Blázquez García  May 17, 2018 to Sean Connelly

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

0
Eduard Lebedyuk · May 16, 2018

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");
0
Sean Connelly · May 16, 2018

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
0
Murillo Braga  May 6, 2020 to Sean Connelly

Hi there Sean,

Do you know whether is there anything to perform the opposite (convert an object into a xml string)?

E.g: 

Class BaseClass.Harvest Extends (%SerialObject, %XML.Adaptor) [ ProcedureBlock ]
{
Parameter ELEMENTQUALIFIED = 1;
...
Property Number As %String(MAXLEN = "", XMLNAME = "Number");
Property PatientNumber As %String(MAXLEN = "", XMLNAME = "PatientNumber");
...

Outcome would be:

<xml>
<Harvest>

   <Number></Number>
   <PatientNumber></PatientNumber>
...

Many thanks

0