Deserializing XML data
Serialization and deserialization of XML input is part of the web services message processing. This cookbook entry describes how a particular set of data is deserialized from an XML instance to a set of Smalltalk objects. The data has two attributes with different names that are of the same complex type.
The XML parsing framework can function in the absence of a schema and mapping specification, but the most control is obtained by using both. This example will illustrate deserializing the same sample XML using
•Neither a schema nor a mapping spec
•A mapping specification only
•Both a schema and a mapping specification
The Visual Programming User Guide has a page named “XML Mapping Components” which gives more information on how the XML components are mapped to Smalltalk classes.
Debugging Tip
The sample shows how to convert with a single map or with a schema assisted by a map. The following expression
AbtXmlMappingError enableExceptionSignal: true
can assist in finding errors in XML data, map or schema in case the deserialization process does not yield the proper results.
Executing the Example Code
Load the VA: XML Examples feature. There are workspace expressions in the ReadMe class method for executing the code.
The Data
The data model for this example consists of a person with two addresses: home and work. The person is similar to the JrcCustomer part found in VA: XML Examples, but because it has two addresses the map and schema files differ structurally from the case where components of complex data type have unique data types.
Smalltalk classes
The Smalltalk classes belonging to the data model are XePerson and XeAddress. The data types of the instance variables for XePerson consist of the complex type XeAddress. A schema and mapping specification are presented in the example that will allow the XML parsing framework to correctly deserialize XML data into Smalltalk objects.
XeAddress has the following methods
•number -- optional apartment number
•street -- house number and street name
•city -- city
•state -- state
•zip -- zip code
XePerson has the following methods
•firstName -- given name
•lastName -- family name
•workAddress --XeAddress specifying location of work
•homeAddress -- XeAddress specifying location of home
XML Data
We will look at two sets of XML Data. The first set has tags which exactly match the name of the Smalltalk class: XePerson. The elements representing the class attributes also exactly match in name and case the getter and setter methods of XeAddress and XePerson. In this case, the default deserialization framework can do some of the work without using a mapping specification.
Note: There must be a namespace attribute in the XML instance data. This namespace must exactly match the namespaceURI in the mapping specification and the targetNamespace of the schema.
XML Dataset 1
This XML instance contains a person with two addresses and has tags corresponding to the attributes of the two classes XePerson and XeAddress. The root tag is XePerson.
Sample xml
<?xml version="1.0"?>
<xe:XePerson xmlns:xe="xeExample" >
<firstName>Joe</firstName>
<lastName>Akarski</lastName>
<workAddress>
<number>99</number>
<street>234 First Avenue</street>
<city>Old Town</city>
<state>AnyState</state>
<zip>45678</zip>
</workAddress>
<homeAddress>
<street>10 Main Street</street>
<city>Anytown</city>
<state>AnyState</state>
<zip>12345</zip>
</homeAddress>
</xe:XePerson>
The XML tags must reflect the hierarchical nature of the data. That is, the address is contained within the person. The tags must also follow the exact order they appear in the schema definitions.
In the above XML notice that one address has a number while the other does not. The optional nature of the number element is specified in the schema.
XML Dataset 2
This XML instance contains a person with two addresses has tags corresponding to the attributes of the two classes XePerson and XeAddress. The root tag is Person, which does not match any class in the example. The home address is capitalized.
Note:
If in your image, you have a Person class defined, unload it to reproduce the results of this example.
Sample xml
<?xml version="1.0"?>
<xe:Person xmlns:xe="xeExample" >
<firstName>Joe</firstName>
<lastName>Akarski</lastName>
<workAddress>
<number>99</number>
<street>234 First Avenue</street>
<city>Old Town</city>
<state>AnyState</state>
<zip>45678</zip>
</workAddress>
<HomeAddress>
<street>10 Main Street</street>
<city>Anytown</city>
<state>AnyState</state>
<zip>12345</zip>
</HomeAddress>
</xe:Person>
.
Dataset 1 Deserialization
Conversion without a Mapping Spec or Schema
In this case, if there is no schema or mapping spec, the resulting object will be an instance of XePerson. The framework was able to resolve the XML tag because a class in the image existed with that name; workAddress cannot be resolved, since there is no class in the image named workAddress or homeAddress. These attributes contain instances of AbtXmlMappedElement. The firstName and lastName tags correspond to attributes which are strings – simple or primitive types—and can be deserialized by default processing.
The Visual Programming User Guide states that: “The XML name and the Smalltalk name are assumed to be the same if no mapping is specified.”
Conversion Using a Mapping Spec
If we add a mapping specification, the above XML data can be converted into equivalent Smalltalk classes using a mapping specification alone. Once again, the result is due to the fact that the tags in the XML instance are exactly the same as the name of the classes and getter/setter methods.
If a mapping spec is added using class element mappings, even though an element mapping is specified for HomeAddress, the framework is unable to resolve the indirect mapping from the subelement homeAddress to the class XeAddress. A schema element must be defined for HomeAddress and a mapping spec defined using class type mapping, as we will show in the next section.
The Visual Programming User Guide states: “The rules of the mapping specification will be applied to determine the correct Smalltalk name for an element within the XML document.” The XML name and the Smalltalk name are assumed to be the same if no mapping is specified. The XML must be well-formed but does not need to conform to any specific shape. All data is stored as strings.”
Sample map:
<?xml version="1.0"?>
<!DOCTYPE XmlMappingSpec SYSTEM "abtxmap.dtd" >
<XmlMappingSpec Name="xePersonmapping.xml"
NameSpaceURI="xeExample">
<ClassElementMapping ElementTagName="workAddress" ClassName="XeAddress">
<AttributeMapping ClassAttribute="number">
<Attribute>number</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="street">
<Attribute>street</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="state">
<Attribute>state</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="city">
<Attribute>city</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="zip">
<Attribute>zip</Attribute>
</AttributeMapping>
</ClassElementMapping>
<ClassElementMapping ElementTagName="homeAddress" ClassName="XeAddress">
<AttributeMapping ClassAttribute="number">
<Attribute>number</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="street">
<Attribute>street</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="state">
<Attribute>state</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="city">
<Attribute>city</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="zip">
<Attribute>zip</Attribute>
</AttributeMapping>
</ClassElementMapping>
</XmlMappingSpec>
The map contains tags corresponding to the class and method names. The string components of the person are defined as attributes in the mapping, but the address components workAddress and homeAddress are defined as subelements. Each has its own <ClassElementMapping> tag with the same mapping in each.
Conversion Using a Schema and a Mapping Spec
The XML data can be deserialized into Smalltalk classes using a schema and class type mappings in the mapping specification. These mappings tie an element tag name to a schema type.
Sample map
<?xml version="1.0"?>
<!DOCTYPE XmlMappingSpec SYSTEM "abtxmap.dtd" >
<XmlMappingSpec Name="xePersonmapping.xml"
NameSpaceURI="xeExample">
<ClassTypeMapping TypeName="XePerson" ClassName="XePerson">
<AttributeMapping ClassAttribute="firstName">
<Attribute>firstName</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="lastName">
<Attribute>lastName</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="workAddress">
<SubElement>Address</SubElement>
</AttributeMapping>
<AttributeMapping ClassAttribute="homeAddress">
<SubElement>Address</SubElement>
</AttributeMapping>
</ClassTypeMapping>
<ClassTypeMapping TypeName="Address" ClassName="XeAddress">
<AttributeMapping ClassAttribute="number">
<Attribute>number</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="street">
<Attribute>street</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="state">
<Attribute>state</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="city">
<Attribute>city</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="zip">
<Attribute>zip</Attribute>
</AttributeMapping>
</ClassTypeMapping>
</XmlMappingSpec>
The Xml in the mapping specification defines how the deserialization process should map the element tags it encounters in the XML instance to methods in the Smalltalk Classes. For example, the <ClassTypeMapping> tag maps the complex schema type Address to the Smalltalk Class XeAddress.
The mapping spec contains tags corresponding to the class and method names. The string components of Person are defined as attributes in the mapping, but the address components workAddress and homeAddress are defined as subelements. Both home and work addresses are subelements that refer to the same complex type Address which is defined once in the sample schema below.
Sample schema
<?xml version="1.0"?>
<!-- Generated by VisualAge Smalltalk on 2019-10-12-09.35.38.147000 -->
<xsd:schema targetNamespace="xeExample"
xmlns:xes="xeExample"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:complexType name="XePerson">
<xsd:sequence>
<xsd:element name="firstName" type="xsd:string"/>
<xsd:element name="lastName" type="xsd:string"/>
<xsd:element name="workAddress" type="xes:Address"/>
<xsd:element name="homeAddress" type="xes:Address" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Address">
<xsd:sequence>
<xsd:element name="number" type="xsd:string" minOccurs="0" />
<xsd:element name="street" type="xsd:string"/>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="state" type="xsd:string"/>
<xsd:element name="zip" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
The schema defines a target namespace which is used in deserializing the XML data.
The tags inside the schema again contain information that links them to the class and method names. The name attribute is that link.
Notice the number element in the complex type specification for the Address. In addition to the type, it specifies a minOccurs attribute of zero. This is the attribute that indicates the element “number” may have zero or one occurrences in an XML instance.
Dataset 2 Deserialization
Conversion without a Mapping Spec or Schema
In this case, if there is no schema or mapping spec, the resulting object will be an instance of AbtXmlMappedRootElement. The framework was not able to resolve the XML tag because no class in the image existed with the name “Person”. The XML tree structure is represented, with all the elements converted to instances of AbtXmlMappedElement, including the simple string types.
Conversion Using a Mapping Spec
If a mapping spec is added using class element mappings, even though an element mapping is specified for HomeAddress, the framework is unable to resolve the indirect mapping from the subelement homeAddress to the class XeAddress. A schema element must be defined for HomeAddress and a mapping spec defined using class type mapping, as we will show in the next section.
As is stated in the Visual Programming User Guide, “the rules of the mapping specification will be applied to determine the correct Smalltalk name for an element within the XML document.” The XML name and the Smalltalk name are assumed to be the same if no mapping is specified. The XML must be well-formed but does not need to conform to any specific shape. All data is stored as strings.
Sample map
<?xml version="1.0"?>
<!DOCTYPE XmlMappingSpec SYSTEM "abtxmap.dtd" >
<XmlMappingSpec Name="xePersonmapping.xml"
NameSpaceURI="xeExample">
<ClassElementMapping ElementTagName="Person" ClassName="XePerson">
<AttributeMapping ClassAttribute="firstName">
<Attribute>firstName</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="lastName">
<Attribute>lastName</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="workAddress">
<SubElement>workAddress</SubElement>
</AttributeMapping>
<AttributeMapping ClassAttribute="HomeAddress">
<SubElement>homeAddress</SubElement>
</AttributeMapping>
</ClassElementMapping>
<ClassElementMapping ElementTagName="workAddress" ClassName="XeAddress">
<AttributeMapping ClassAttribute="number">
<Attribute>number</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="street">
<Attribute>street</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="state">
<Attribute>state</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="city">
<Attribute>city</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="zip">
<Attribute>zip</Attribute>
</AttributeMapping>
</ClassElementMapping>
<ClassElementMapping ElementTagName="homeAddress" ClassName="XeAddress">
<AttributeMapping ClassAttribute="number">
<Attribute>number</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="street">
<Attribute>street</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="state">
<Attribute>state</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="city">
<Attribute>city</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="zip">
<Attribute>zip</Attribute>
</AttributeMapping>
</ClassElementMapping>
</XmlMappingSpec>
The map contains tags corresponding to the class and method names. The string components of the person are defined as attributes in the mapping, but the address components workAddress and homeAddress are defined as subelements. Each has its own <ClassElementMapping> tag with the same mapping in each.
Conversion Using a Schema and a Mapping Spec
Using both a mapping specification and a schema provides the most control over how deserialization proceeds.
Sample map
<?xml version="1.0"?>
<!DOCTYPE XmlMappingSpec SYSTEM "abtxmap.dtd" >
<XmlMappingSpec Name="xePersonmapping.xml"
NameSpaceURI="xeExample">
<ClassTypeMapping TypeName="XePerson" ClassName="XePerson">
<AttributeMapping ClassAttribute="firstName">
<Attribute>firstName</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="workAddress">
<SubElement>Address</SubElement>
</AttributeMapping>
<AttributeMapping ClassAttribute="lastName">
<Attribute>lastName</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="homeAddress">
<Attribute>HomeAddress</Attribute>
</AttributeMapping>
</ClassTypeMapping>
<ClassTypeMapping TypeName="Address" ClassName="XeAddress">
<AttributeMapping ClassAttribute="number">
<Attribute>number</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="street">
<Attribute>street</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="state">
<Attribute>state</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="city">
<Attribute>city</Attribute>
</AttributeMapping>
<AttributeMapping ClassAttribute="zip">
<Attribute>zip</Attribute>
</AttributeMapping>
</ClassTypeMapping>
</XmlMappingSpec>
The Xml in the mapping specification defines how the deserialization process should map the element tags it encounters in the XML instance to methods in the Smalltalk Classes. For Example the <ClassTypeMapping> tag maps the complex schema type Address to the Smalltalk Class XeAddress.
The mapping spec contains tags corresponding to the class and method names. The string components of Person are defined as attributes in the mapping, but the address components workAddress and homeAddress are defined as subelements. Both home and work addresses are subelements that refer to the same complex type Address which is defined once in the schema below.
Sample schema
<?xml version="1.0"?>
<!-- Generated by VisualAge Smalltalk on 2019-10-12-09.35.38.147000 -->
<xsd:schema targetNamespace="xeExample"
xmlns:xes="xeExample"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:complexType name="XePerson">
<xsd:sequence>
<xsd:element name="firstName" type="xsd:string"/>
<xsd:element name="lastName" type="xsd:string"/>
<xsd:element name="workAddress" type="xes:Address"/>
<xsd:element name="HomeAddress" type="xes:Address" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Address">
<xsd:sequence>
<xsd:element name="number" type="xsd:string" minOccurs="0" />
<xsd:element name="street" type="xsd:string"/>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="state" type="xsd:string"/>
<xsd:element name="zip" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="Person" type="xes:XePerson" />
</xsd:schema>
The schema must define a target namespace which matches that of the XML data and the mapping specification.
The tags within the schema again contain information that links them to the class and method names. The name attribute is that link.
The important differences to note in this schema, is
•the change in capitalization of the XePerson element homeAddress to HomeAddress to match the tag in the XML instance and
•the addition of an element with name Person and type XePerson.
These changes allow deserialization of tags which do not exactly match the Smalltalk class and methods names.
Summary
This cookbook example has illustrated how to use schema and mapping specifications to deserialize data whose components share a complex type.